Регистрация Главная Сообщество
Сообщения за день Справка Регистрация
Навигация
Zhyk.org LIVE! Реклама на Zhyk.org Правила Форума Награды и достижения Доска "почета"

Ответ
 
Опции темы
Старый 25.10.2023, 14:41   #1
 Разведчик
Аватар для vivec
 
vivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небе
Регистрация: 25.10.2023
Сообщений: 2
Популярность: 746
Сказал(а) спасибо: 0
Поблагодарили 3 раз(а) в 2 сообщениях
 
По умолчанию [DevLog] Разработка пакетного бота

Приветствую всех.

В этом треде я буду вести лог разработки пакетного бота. Конечная цель - сделать работающий бот, который летает по карте и собирает коробки. Надеюсь, что кому-то это будет интересно, и я всё это делаю не зря.
Сразу скажу, что я давно не играл в DO, поэтому про новые фишки не знаю, а старые уже подзабыл. В любом случае, буду опираться на опыт, который у меня уже есть.

Итак, приступим.

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


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

Не вопрос - скачиваем. Первое, на что обращаем внимание - это вот эти библиотеки:


Запускаем сам клиент. Очевидно, что это браузер Qt5WebEngine, работающий на базе Chromium. Логинимся в аккаунт - и вот она: долгожданная кнопка "START"!


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

Пока в этом не особенно много смысла. Чтобы что-то узнать - нужно увидеть трафик. Я это буду делать через программу WireShark.

Запускаем WireShark, проделываем те же самые действия, и смотрим в лог:


Здесь видно, что для коммуникации с сайтом используется хедер
Код:
User-Agent: BigpointClient/1.6.9
Вот ещё один интересный запрос:


Здесь присутствует хедер
Код:
X-Requested-With: ShockwaveFlash/26.0.0.137
Это нам понадобится в будущем для авторизации. Пока что мы просто смотрим, что здесь вообще происходит.

Так же видим запрос к maps.php.


Ответом на него сервер отдаёт XML'ку с IP-адресами карт:


Нажимаем кнопку "START". В логах начинают появляться запросы на скачивание .swf файлов:


Насколько я помню, main.swf - это как раз то, что нам нужно. Скачиваем эти файлы и складываем в специальную папку, они нам пригодятся.

Заодно посмотрим TCP-пакеты. Проблема WireShark'а в том, что несмотря на всю свою мощь, он не может ловить пакеты от определённого процесса. Он работает на уровне интерфейсов, а там никакой информации про процессы нет. Но, к счастью, чуть выше мы получили IP адреса карт. В моём случае их можно описать маской 47.245.0.0/16. Пишем следующий фильтр:
Код:
ip.addr == 47.245.0.0/16


Я не особо силён в TCP протоколе, если увидите неточности - поправляйте.
Пакет [SYN, ACK] с SACK_PERM=1 - это подключение к сокету. Пакет [FIN, ACK] - это отключение.
Тут мы видим 2 подключения и 1 отключение. При первом подключении отправляется вот такой пакет:


На который приходит вот такой ответ:


После этого соединение прерывается.
Дальше соединение устанавливается заново, и клиент отправляет серверу вот такой пакет:


На что получает следующий ответ:


В запросе первые 4 байта (0x24) - это длина пакета(40), минус 4 байта. Дальше идёт, предположу, что ID сообщения(0x029A), после - длина строки(0x0020), и сама строка.

В ответе есть небольшое отличие. Длина пакета записана в первых трёх байтах, а не четырёх(0x25). Всё остальное в целом такое же.

Итак, базовая информация у нас есть. Следующий шаг - добраться до внутренностей main.swf

Добавлено через 1 час 9 минут
Для работы с SWF я буду использовать программу JPEXS Flash Decompiler. Её совершенно бесплатно можно скачать с github'а: [Ссылки могут видеть только зарегистрированные пользователи. ]

Разумеется, сначала я попытаюсь открыть main.swf
К сожалению, хотя и ожидаемо - получаю ошибку:


С loadingscreen.swf то же самое.

Значит, начнём с preloader.swf


Он открывается без проблем. Попытаемся "наудачу" грепнуть main - и о чудо! Находим то, что искали:


В этом же файле, чуть ниже видим процесс получения валидного main.swf:


Сначала скачивается raw data, потом распаковывается(используется упаковщик ZLib), после чего расшифровывается.
За расшифровку отвечает method_48:


Дальше идёт class_15:


Который, в свою очередь, использует class_27:



Так, надо притормозить. В теории это, конечно же, можно расшифровать. Но, насколько мне известно - BigPoint часто меняет протокол, и наверняка алгоритм шифрования тоже. Мы не хотим, чтобы после очередного обновления протокола бот перестал работать, а значит извлечение main.swf нужно автоматизировать. Я, к сожалению, не смог найти интерпретаторы ActionScript 3, поэтому подход придётся поменять.

У JPEXS есть прекрасная функция - Search SWFs in memory. Так как SWF после расшифровки неизбежно попадает в память - я думаю, что это именно то, что нам нужно. Запускаем игру и смотрим:


Методом тыка мне удалось выяснить, что это и есть тот самый main.swf
Почему он представлен в 3 экземплярах? Я не знаю.
Почему он весит 12 мегабайт, а не 5, как оригинальный? Это мне тоже неизвестно.

В любом случае - он у нас есть. Попробуем грепнуть какой-нибудь пакет. К примеру, 0x029A, который мы получили в прошлый раз. 0x029A - это 666. Стоит отметить, что это достаточно требовательная к ресурсам ПК задача:



И вот результат:



Итак, мы добрались до сетевого кода!
Этот пакет отвечает за версию клиента, на что ему в ответ сервер возвращает такую же версию, плюс некий boolean. Для удобства я экспортировал исходники, чтобы не нагружать компьютер при каждом грепе:



Теперь мы можем посмотреть и остальные пакеты, которые поймали ранее.



Номер пакета - 7. Это LoginRequest. Выглядит он так:


Обратите внимание на этот код:
Код:
param1.writeInt(this.userID << 3 | this.userID >>> 29);
Если сложить количество бит, на которое число сдвигается влево, с количеством бит, на которое число сдвигается вправо - то в данном случае мы получим 32. В других случаях мы всегда будем получать число бит, из которых состоит число. Например для short, он же int16 - сумма будет равна 16. Для int8 сумма будет равна 8.
Это значит, что BP использует wrapping bit shift, он же rotate. Вот как он работает:


Для обратного преобразования нужно просто поменять числа местами.

Теперь, если разобрать пакет, который уходит на сервер - то мы получим следующее:

user_id = 0x52685300 << 29 | 0x52685300 >> 3 = 0x00 | 0xA4D0A60 = 172821088 - это мой User ID
faction_id = 0x0180 << 9 | 0x0180 >> 7 = 0x00 | 0x03 = 3 - это VRU.
session_id = "b8f61bde4c7d01d74ba8475750b86661" - это SID.
version = "" - пустая строка. Видимо, тут должна быть(или когда-то была) версия.
instance_id = 0x002D7000 << 20 | 0x002D7000 >> 12 = 0x00 | 0x02D7 = 727 - если я правильно понял, то это ID клиента, через который я захожу в игру.
В конце идёт boolean(var_5664), который в моём случае равен 1. Если покопаться в исходниках, то мы увидим вот такой участок кода:
Код:
_loc2_.var_5664 = Settings.platform == Settings.platformTypeClient;
И вот такой:
Код:
public static var platformTypeWeb:String = "WEB";
public static var platformTypeClient:String = "PC_CLIENT";
Это отголоски прошлого, когда игру можно было запустить из браузера.


Следующий шаг - повторить всё то же самое, но программно.

Последний раз редактировалось vivec; 25.10.2023 в 19:11. Причина: Добавлено сообщение
  Ответить с цитированием
2 пользователя(ей) сказали cпасибо:
cheLink (03.03.2024), HeaDShoTRuS (01.01.2024)
Старый 08.11.2023, 07:19   #2
 Разведчик
Аватар для Bug.Point
 
Bug.Point никому не известный тип
Регистрация: 13.03.2022
Сообщений: 1
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 1 раз в 1 сообщении
 
По умолчанию Re: [DevLog] Разработка пакетного бота

Цитата:
Сообщение от vivecПосмотреть сообщение
Приветствую всех.

В этом треде я буду вести лог разработки пакетного бота. Конечная цель - сделать работающий бот, который летает по карте и собирает коробки. Надеюсь, что кому-то это будет интересно, и я всё это делаю не зря.
Сразу скажу, что я давно не играл в DO, поэтому про новые фишки не знаю, а старые уже подзабыл. В любом случае, буду опираться на опыт, который у меня уже есть.

Итак, приступим.

....

Привет, товарищ! Пожалуйста, не забрасывай тему, всё очень интересно. Вижу последнее редактирование от 25.10, но ты был в сети 05.11. Отклика народа нет, форум почти мертвый, но след останется навсегда (интернет помнит всё) и обязательно кто-то увидит и ему это пригодится.

К сожалению, у меня нет возможности поставить "спасибо" на твое сообщение, поэтому могу сказать это лишь письменно.

Уже есть несколько вопросов. Я ушел из ДО, надоело, хоть и раньше сидел, как на наркотике, возвращаясь каждый год / полтора, но сейчас тошно. Есть новая игрушка, она сразу идет на платформах IOS, Android, Linux, Windows, Mac. Играю на Windows, так там exe файл открывается через 7zip и есть доступ ко всей начинке. Если правильно понимаю, то движок - java. Хочу написать бот, пакетник. Кликеры уже делал, есть, но это не то, нужно что-то посерьезнее. Теперь ломаю голову, как залезть, перехватить и потом имитировать игровой процесс программно.

1. Доступ ко всем файлам есть

2. Игра создает скрытую папку с txt файлом на диске C, где записаны все настройки. Разрешение экрана, кнопки, логин-пароль от всех сохранённых аккаунтов.

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

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

Грубо говоря, твоя тема сейчас для меня пример, хоть и бот для flash игры.

Писать буду на с++ или змейке. Это особо не важно, стандартную информацию и гайды найти можно. Но, с ботами такого типа не нашел. Для меня идеалом является DarkBOT. Это лучшее, что я видел. Стремлюсь к тому, чтобы сделать нечто подобное.

P.s. Моя цель - вдохнуть жизнь в сообщество читеров. Пробудить былой интерес у людей к нестандартным решениям и коллективному творчеству.

Последний раз редактировалось Bug.Point; 10.11.2023 в 19:15.
  Ответить с цитированием
Старый 13.12.2023, 05:55   #3
 Разведчик
Аватар для vivec
 
vivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небеvivec лучик света в грозовом небе
Регистрация: 25.10.2023
Сообщений: 2
Популярность: 746
Сказал(а) спасибо: 0
Поблагодарили 3 раз(а) в 2 сообщениях
 
По умолчанию Re: [DevLog] Разработка пакетного бота

Извиняюсь за долгое отсутствие, было много работы. Давайте продолжим.

Bug.Point, спасибо за проявленный интерес! Приятно видеть, что в комьюнити ещё есть люди. Надеюсь, смогу ответить на твои вопросы в этом и следующих постах.




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


Вот примерный план, которого я буду придерживаться:
  1. Авторизация, получение куков, SID'а и прочей информации
  2. Подключение к сокету
  3. Де/сериализатор пакетов DO, устойчивый к изменениям протокола
  4. Графический интерфейс
  5. И, наконец, сам бот
За прогрессом можно будет следить в моём GitHub-репозитории: [Ссылки могут видеть только зарегистрированные пользователи. ]


Начнём с пункта 1.
Для начала нам нужно создать проект. Клонируем пустой репозиторий, делаем в него "cd" и пишем "cargo init".
Появились 3 новых файла: .gitignore, Cargo.toml и main.rs


В .gitignore прописана папка /target, так как в неё будут складываться артефакты сборки, незачем пушить такое в репозиторий.
В Cargo.toml находится описание проекта.
В main.rs сам код.

Это скелет самого простого Rust-проекта, состоящего из 1 бинарника. Если мы в будущем не хотим запутаться во всех переплетениях кода - то лучше этот скелет немного поменять. А именно - превратить его в workspace, то есть работать мы будем с подпроектами.
Сначала создадим первый подпроект "urdobot-app" командой "cargo new urdobot-app".
Затем поменяем корневой Cargo.toml таким образом:


Как мы видим - структура подпроекта urdobot-app абсолютно такая же, как и у нашего изначального проекта urdobot.
Я хочу, чтобы бот был представлен одним .exe-шником.
Один .exe-шник у нас уже есть - это корневой проект "urdobot". Поэтому сделаем "urdobot-app" библиотекой.
Для этого нужно просто переименовать его "main.rs" в "lib.rs"

Чтобы использовать эту библиотеку корневом проекте - нужно добавить её в секцию "dependencies":


Давайте теперь попробуем её использовать. Объявим публичную функцию в "urdobot-app/src/lib.rs", которая будет печатать сообщение:


И вызовем её в "main.rs":


Теперь пишем "cargo run" - и видим, что всё работает!


Можно приступать к авторизации.
Для работы с HTTP-запросами в Rust'е есть библиотека "reqwest". Сделаем "cd" в папку "urdobot-app" и напишем "cargo add reqwest".
Эта команда сама найдёт последнюю версию нужной нам библиотеки и добавит её в "Cargo.toml":


Так же нам нужна библиотека "reqwest-cookie-store", которая сохраняет куки между запросами. Её добавляем таким же способом:


В языке Rust библиотеки называются крейтами. Поэтому далее я буду называть их именно так.
Начнём с загрузки страницы входа. Для этого сначала создадим клиент и установим ему "User Agent", значение которого мы уже знаем из первого поста.
Это делается вот так:


Как мы видим - переменная "client" имеет тип "Result<Client, Error>". "Result" - это очень распространённый тип в языке Rust, который нужен для обработки ошибок.
В нём хранится либо интересующее нас значение, либо ошибка.
Для того, чтобы достать из него тип "Client", есть несколько способов.
Например, можно сделать "unwrap()". В таком случае, если в нём хранится значение - то оно достанется и программа продолжит выполнение. А если ошибка - то программа упадёт.
Падение после "unwrap()" никак нельзя откатить. Этот метод лучше не использовать без необходимости. В данном случае он нам не подходит.

Вместо этого можно воспользоваться паттерн-матчингом:


Ok и Err - это два возможных варианта типа Result.
Теперь у нас есть переменная с типом "Client", и если нам не удалось сконструировать клиент - то функция "hello" просто вернёт управление функции "main".

Выглядит весьма громоздко. Например, что будет, если таких проверок нам надо будет сделать 10? Желательно с одинаковой обработкой в случае ошибки.
Можно избавиться от вызова "eprintln!" и возвращать из функции "Result", который, в свою очередь, будет обрабатываться в "main".
Так как сама функция ещё ничего не возвращает - то тип в случае успеха будет "()". А в случае ошибки - "reqwest::Error" (пока что):




Но даже такую конструкцию можно сократить. В Rust'е есть специальный оператор - "?". Он делает буквально то, что вы видите на предпоследнем скриншоте. Матчит "Result", и если в нём лежит ошибка - то делает "return".
Вот так с ним будет выглядеть функция "hello":


Теперь нужно загрузить страницу входа.
Дело в том, что методы клиента, такие как "get", "post" и т.д. - асинхронные. Они помечены как "async", и для получения результата их выполнения нужно сделать "await".
"await" нельзя вызвать из обычной функции. Поэтому функцию "hello" нужно тоже сделать асинхронной:


Но мы вызываем эту функцию из "main". А сам "main" просто так пометить как "async" не получится.
Для выполнения асинхронных функций нам нужен асинхронный рантайм. Я возьму самый популярный - "tokio".
Чтобы его добавить - нам будет недостаточно простого "cargo add tokio". Для полноценной работы надо активировать так же две фичи - "macros" и "rt-multi-thread"
Команда будет выглядеть вот так: "cargo add tokio -F macros -F rt-multi-thread"

Теперь мы можем спокойно объявить "main" как "async", предварительно написав перед ним "#[tokio::main]"


Вернёмся к функции "hello".
Мы сделали запрос к DO, теперь нужно получить ответ. Метод "send().await" возвращает нам "Result", который мы распаковываем так же, как и предыдущий - через вопросительный знак. И получаем тип "Response".
У этого типа есть метод "text()", тоже асинхронный и тоже возвращающий "Result". С помощью него достанем HTML:


И, для удобства, сохраним полученный HTML в файл.
Для сохранения в файл нам нужно сначала создать файл через "std::fs::File::create".
Этот метод возвращает "Result", но тип ошибки у него уже не "reqwest::Error", который возвращает наша функция, а "std::io::Error". Поэтому развернуть его через вопросительный знак мы в данный момент не можем.
Для решения этой проблемы нужно установить крейт "anyhow". Он позволяет приводить все типы ошибок к "anyhow::Error", и предоставляет свой алиас на тип "Result".
Чтобы каждый раз не писать "anyhow::Result", его можно импортировать.
Выглядеть это будет вот так:


Для записи в файл воспользуемся функцией "write_all()". Она недоступна без определённого импорта, но IDE подскажет, какой импорт нам нужен, а так же сама его подставит:


Обратите внимание, что "write_all()" принимает "&mut self", а это значит, что переменная "file" должна быть помечена как "mut":


Наличие "mut"-ссылки на переменную гарантирует, что в данный момент во всей программе больше нет ни одной ссылки на эту переменную. Если мы записываем данные в файл - то в этот момент мы больше ничего не можем делать с этим файлом.
Так же "mut" позволяет модифицировать переменную.

Теперь переходим в корневой проект, пишем "cargo run" - и смотрим, что получилось. В корневой папке появился файл "index.html". Вот так он выглядит, прямо как в клиенте DO:


А что внутри?


Для авторизации нам надо POST-запросом отправить форму с полями "username" и "password" на адрес, указанный в атрибуте "action".
Этот адрес надо каким-то образом вытащить. Можно через regex, но это не очень хорошее решение.

Для парсинга HTML в Rust'е есть крейт от CloudFlare, который называется "lol-html". Добавим его в "urdobot-app".
Так же нам пригодится крейт "html-escape", который нужен для парсинга HTML-символов, таких как "&amp;".


Для поиска нужных элементов используется CSS-селектор. Он задаётся строкой, и внутри него должны присутствовать двойные кавычки.
Чтобы не экранировать их - в Rust'е есть механизм под названием "Raw string literal". Такая строка задаётся не двойными кавычками, а двойными кавычками + произвольным набором символов, которые начинают и заканчивают объявление строки.
Например - как на скриншоте: r#"[name="bgcdw_login_form"]"#

Запускаем:


Отлично! Теперь можно отправлять форму.
Сначала включим в клиенте настройку, отвечающую за сохранение куков:


Для ввода логина и пароля пока что будем использовать консоль:


Всё, что нам теперь остаётся - это просто отправить форму. Так же сохраним результат в "account.html":


Запускаем:


Смотрим что получилось:


Из первого поста мы знаем, что для запуска самой игры нам нужно 4 значения: user_id, faction_id, session_id, и instance_id. Давайте их вытащим.

Для начала создадим вот такую структуру:


На странице с аккаунтом мы не можем получить session_id. Его нужно вытаскивать из "/indexInternal.es?action=internalMapRevolution".
Для этого запроса нам надо узнать, на каком сервере мы играем. Эта информация есть в ответе сервера, который мы получили шагом ранее. Надо немного поменять код:


В моём случае это будет "int14.darkorbit.com"

Теперь мы можем сделать GET запрос к "indexInternal.es", результат которого сохраним в файл "revolution.html":


Запускаем и смотрим. На самой страничке ничего интересного нет:


Всё интересное находится внутри:


Нам остаётся только вытащить этот объект, а потом вытащить из него интересующие нас поля.
Так как это не HTML - вытаскивать можно regex'ом. Для этого нужен крейт "regex":




Видно, что это валидный JSON. Для парсинга воспользуемся крейтами "serde" с фичей "derive" и "serde-json".
Команда для установки: "cargo add serde serde-json -F serde/derive"
Теперь немного поменяем наш код.
Дело в том, что все значения в этом JSON'е - это строки. Некоторые из них надо перевести в числа.
Так же в Rust'е имена переменных должны быть строго в snake_case.
В крейте "serde" это всё предусмотрено. Структура теперь выглядит вот так:


Осталось её распарсить:




Давайте вернём её из функции. Для этого поменяем тип возвращаемого значения с "Result<()>" на "Result<UserData>", уберём последний "println!", и вместо "Ok(())" напишем "Ok(serde_json::from_str(data)?)".
На основе возвращаемого значения Rust сам поймёт, какой тип ему нужно парсить.
В самом "main.rs" немного поменяем "match":


И вот результат:


Эти данные можно отправлять на сокет.
  Ответить с цитированием
Пользователь сказал cпасибо:
HeaDShoTRuS (01.01.2024)
Старый 27.02.2024, 15:43   #4
 Разведчик
Аватар для RoMD
 
RoMD никому не известный тип
Регистрация: 23.05.2013
Сообщений: 1
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
 
По умолчанию Re: [DevLog] Разработка пакетного бота

Автору большое спасибо что проявляет интерес к ДО, сам уже лет 10 не играл в ДО но воспоминания очень хорошие.
Продолжай это дело!
  Ответить с цитированием
Ответ


Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
[Программа] Разработка бота для Hot Dance Party(HDP) ANS Прочее 101 16.06.2011 23:36

Заявление об ответственности / Список мошенников

Часовой пояс GMT +4, время: 13:15.

Пишите нам: [email protected]
Copyright © 2024 vBulletin Solutions, Inc.
Translate: zCarot. Webdesign by DevArt (Fox)
G-gaMe! Team production | Since 2008
Hosted by GShost.net