Радар своими руками /16.12.2008/
Введение
В этой статье описывается создание "радара" - кода отображающего других игроков внутри клиента ла2. В ней рассмотрены следующие вопросы: рисование в окне клиента, получение информации об нпс и игроках хранящейся клиенте, перехват входящего пакета, перехват исходящего пакета, отправка пакета клиенту.
В свое время, когда я начинал разбираться с клиентом, я искал подобную статью, так и не нашел. Ну а раз ее нет - значит ее надо написать чтобы она была. Для понимания этой статьи необходимы базовые знания по работе клиента ла2, реверсированию и программированию. Все примеры относятся к клиенту C6, но я думаю клиенты ла2 принципиально не отличаются друг от друга.
1. Общая информация о клиенте ла2
Для начала небольшая вводная об устройстве клиента ла2. Он состоит из 1 .exe модуля и нескольких длл выполняющих строго определенные функции - GUI (nwindow.dll), движок (engine.dll), общие вспомогательные функции (core.dll). Клиент сделан на основе движка unreal engine, написан на VC++ с применением ООП. Длл экспортируют практически все методы основных классов, имена декорированы (mangling), также экспортируются таблицы методов (vftable).
2. Рисование
Основная функция радара - рисовать цели в окне клиента. Для этого нужны функции рисования, и перехват нужного участка кода клиента для вызова кода рисования радара.
Клиент для рисования как правило использует класс FCanvasUtil. Для его использования надо создать экземпляр объекта, после рисования - уничтожить его. Примерный размер объекта не более 256 байт (точное значение неизвестно). В классе FCanvasUtil есть одна нормальная функция рисования
void __thiscall FCanvasUtil::DrawLine(float x1, float y1, float x2,float y2,class FColor color,int unk) ,
где x1..y2 - четыре экранные координаты в пикселах,
color - цвет, dword 0xSSRRGGBB (SS - Saturation),
unk - неизвестный параметр, обычно равнен нулю.
Точки можно рисовать короткими штрихами, окружности аппроксимировать отрезками (или пунктиром).
Рисовать надо вместе с клиентом, в одном из методов xxx::Render(), так чтобы картинка радара рисовалась последней (верхней), порядок вызова методов клиентом неизвестен, но рисование после FPlayerSceneNode::Render() позволяет нарисовать радар правильно.
3. Получение информации о целях
Все объекты на карте поделены в клиенте на категории ("живые",лут,...) и хранятся в нектором списке. Для получения "живых" объектов (игроки,НПС) используется метод
struct User * __thiscall UNetworkHandler::GetNextCreature(float Radius,int PrevID) ,
где Radius - радиус поиска, PrevID - ИД предыдущей цели.
Метод возвращает указатель на структуру содержащую информацию о цели (см. приложение). Метод может возвращать ноль, видимо при отсутствии целей вообще. Метод можно вызывать бесконечное число раз, видимо список объектов цикличен, для того чтобы определить конец списка надо проверять повторы ИД возвращаемых целей. Перебор целей можно начинать как с PrevID=0 так и PrevID=-1 .
4. Получение информации о своем персонаже - перехват S->C пакета
Для получения структуры User по ID объекта надо вызвать метод
struct User * __thiscall UNetworkHandler::GetUser(int ID) .
ИД своего персонажа можно получить перехватив S->C пакет UserInfoPacket (#4). Теоретически этот ID можно получить в клиенте по статическому адресу, но перехват пакета очевиднее.
S->C пакеты обрабатываются в клиенте следующим образом:
В отдельном потоке, в цикле, клиент принимает пакеты вызывая ws2_32.recv(), расшифровывает их, добавляет в очередь пакетов методом
UNetworkHandler::AddNetworkQueue(NetworkPacket *) .
Структура NetworkPacket содержит отдельно ID пакета, размер данных пакета (без ID), данные пакета (без ID).
В методе UNetworkHandler::Tick(float) клиент достает пакеты из очереди методом
UNetworkHandler::DispatchNetworkQueue(NetworkPacke t * *) .
Затем по ID пакета вызывается обработчик пакета, по указателю таблицы обработчиков пакетов. Размер элемента таблицы равен 0x104 байтам - указатель на обработчик и UNICODE-имя пакета. Прототип обработчика пакета:
typedef void (__cdecl TPacketHandler)(UNetworkHandler *,char *data);
где data - указатель на данные пакета, без его ID.
Обработчик пакета дизассемблирует пакет согласно его формату, вызывает соответствующий метод UGameEngine::OnXXX() и записывает информацию о пакете во внутренний лог клиента.
Метод UGameEngine::OnXXX вызывает события в GUI или в движке клиента.
Очевидно, что перехватывать одиночный пакет удобно подменяя указатель на обработчик пакета или указатель на метод UGameEngine в его таблице методов.
5. Управление настройками радара - перехват C->S пакета и отправка пакета клиенту
Внутри клиента можно организовать достаточно удобный интерфейс используя HTML встроенный в клиент.
Для показа диалогового окна пользователю, надо отправить клиенту пакет с HTML-кодом, методом
int __thiscall UGameEngine::OnNpcHtmlMessage(struct User *npc,unsigned short *html,int unk) ,
где npc - указатель на структуру User НПС отправившего сообщение, может быть равен нулю,
html - указатель на UNICIDE-строку с HTML-кодом,
unk - неизвестный параметр, обычно равен нулю.
Если ввод данных производится кнопками, то для перехвата ввода необходимо перехватить пакет RequestBypassToServer. Клиент отправляет пакеты следующим образом:
GUI или движок клиента вызывают соответствующий метод UNetworkHandler, например
int __thiscall UNetworkHandler::RequestBypassToServer(class L2ParamStack &) .
Этот метод передает формат пакета и его данные в неэкспортируемую функцию отправки пакета
void __cdecl SendPacket(UNetworkHandler*,char*,...) .
Функция отправки пакета ассемблирует пакет в буфер согласно его формату, шифрует и отправляет на сервер через ws2_32.send() .
Для перехвата одиночного отправляемого пакета удобно перехватить указатель на метод в таблице методов UNetworkHandler.
Заключение
Я не выкладываю сам исходник радара, в его коде нет ничего сложного, и имея информацию изложенную в этой статье его можно достаточно быстро собрать.
Эта статья больше посвящена частям клиента относящимся к пакетной части клиента. Графическая часть рассмотрена вскользь без какого-либо разбора, движок клиента только упоминается, совсем не рассмотрены GUI и остальные части клиента. Буду рад если кто-нибудь дополнит эту статью или выложит что-то новое. Возможно есть смысл перевести ее на английский.
Приложение. Структуры клиента (С6)
Код:
struct User
{
int _zunk1_12[2];
int user_type; //ZERO for player
int _zunk1_34[2];
int unk_npc_spec;
int id;
unsigned short name[0x30/2];
int race;
int _zunk2;
int _xunk3;
int lvl;
int _xunk4;
int _zunk5;
int states[6];
int hp_max;
int hp;
int mp_max;
int mp;
int _xunk68[3];
int wear[14];
char _zunk9[0xC0];
int Color;
char _unk10[0x70];
APawn *Pawn;
int _zunk11[3];
int _xunk12;
int sp;
int accuracy_;
int critical;
int _xunk13;
int _xunk14;
int patk;
int evasion_;
int matk;
int mdef;
int castspd;
char _unk15[0x6C];
int atkspd;
char _unk16[0x14];
int cp_max;
int cp;
//current size=0x2CC, real size = 0x380
};
class APawn
{
char _unk[0x1BC]; //...
public:
FVector Location;
//...
};
class FVector
{
public:
float X,Y,Z;
};
----------------------------------------------------
[C] GoldFinch
Спасибо
Arrowdodger за помощь в написании и тестировании радара
Разработчикам BSFG (Abyss) за их радар и его кривую защиту. Кстати код у binkw32.dll тоже хреновый :)