Как известно QT — это кросс-платформенная библиотека, позволяющая достаточно быстро создавать приложения. С появлением в ней встроенного веб-браузера webkit, ее стало возможно использовать для написания ботов для веб-игр, чем я и решил заняться несколько дней назад.Сразу надо сказать что использование webkit подходит не для всех игр, а только для игр не проверяющих использование браузера IE.
Для создания бота через webkit можно пойти двумя путями:
- Использовать QTestLib и имитировать действия пользователя.
- Использовать QWebFrame->evaluateJavaScript
Использование QTestLib
При использовании QTestLib мы создаем собственную очередь сообщений, которые передаем указанному виджету (в данном случае виджету QWebView). Очередь сообщений может содержать события нажатий кнопок, движения мыши, клики мышкой (как одинарные так и двойные). Использование этой библиотеки позволяет, в теории, создать бота для любой игры, даже игры, написанной на flash, однако ее использование достаточно трудоемко.
Использование QWebFrame->evaluateJavaScript
Данный метод заключается в использовании вставок JavaScript, которые будут исполняться в контексте текущего фрейма. Таким образом можно написать бота только для игры, полностью построенной на html.
Построения бот-программы
Основной задачей бот-программы, кроме того, что она должна ботить, является скрытность, программа не должна выдавать себя. В идеале она должна вести себя точно также как и обычный игрок.
Я писал бота для иры Carnage. Боту был выбран следующий алгоритм работы:
- Зайти в игру.
- Дождаться восстановления хитов
- Создать заявку на хаотичный бой
- Провести бой
- Перейти к пункту 2
Заход в игру делается с нажатия кнопки Login в интерфейсе программы самим пользователем. При нажатии кнопки Login необходимо запустить примерно такой скрипт:
1 2 3 |
document.login.value='You name'; document.password.value='You password'; document.form.auth-form.submit(); |
Для выполнения этого скрипта необходимо получить QWebFrame на указанную страницу:
1 |
QWebFrame *frame = webView->page()->frameAt(QPoint(100,100)); |
Конкретно в данном случае получение фрейма по конкретным координатам не критично, но в дальнейшем это очень пригодится. Дело в том, что в игре Carnage используется несколько разных фреймов, поэтому нам необходимо получать именно требуемый фрейм, а точка 100,100 в моем случае находится именно в нем.
И выполнить JavaScript:
1 |
frame->evaluateJavaScript(script); |
После логина начинается самое интересное. Для каждой страницы необходим свой скрипт, а на некоторые страницы (например на страницу подачи заявки) надо даже несколько скриптов которые будут вызваны с некоторым интервалом. Это необходимо, потому что в игре активно используется innerHTML и редирект фрейма на туже самую страницу но с другими параметрами. Чтобы не усложнять себе жизнь и не анализировать содержимое страницы проще сделать задержки.
Скрипты, по вполне понятным причинам, следует выполнять только после того, как страница полностью загрузится. Для этого можно использовать слот: void loadFinished ( bool ok );
Внутри этого слота я проверяю на какой странице сейчас находится требуемый фрейм:
1 2 3 |
QWebFrame *frame= webView->page()->frameAt(QPoint(100,100)); QString path = frame->url()->path(); if(path=="/zayavka.pl")... |
Получив требуемый адрес мы запускаем таймер на некоторую функцию, которая произведет обработку страницы с помощью JavaScript. Почему именно таймер? Просто из слота необходимо выйти как можно быстрее, а использовать потоки не очень хорошо, потому что у QT имеются на этот счет некоторые нехорошие мысли, когда я попытался использовать потоки то это часто приводило к падению приложения. Используя таймеры мы убиваем сразу двух зайцев — мы решаем проблему быстрого возврата из слота и мы решаем проблему задержек при заполнении данных на страницах. Мы не можем использовать зацикленный JavaScript код, потомучто это приводит к тому, что основной JavaScript код страницы перестает выполняться. И мы не можем сделать sleep в основном потоке, потому что тогда опять таки перестанет выполняться JavaScript на странице.
Возврат значений из JavaScript
Временами возникает необходимость получать некоторые значения от JavaScript кода. Сделать это можно через возвращаемое значение функции evaluateJavaScript. Данная функция возвращает QVariant содержащий результат вывода вашего скрипта. И например вот такой вот скрип, запущенный в Carnage, вернем вам количество ваших хитов:
1 |
top.currHP; |
Собственно это все что необходимо чтобы написать бота для игры Carnage, Torment и еще десятка клонов этих игр.
Хе-хе.
А у Qt есть ограничение на потоки: доступ к gui-объектам должен производиться только из основного потока.
admin Reply:
2 марта, 2010 at 15:06
На самом деле как показала практика не из основного потока можно вызывать функции которые не модифицируют объект, но как только дернишь чтонить что его модифицирует — все, прощай приложение.
Что надо сказать очень неудобно… Могли бы и thread-safe добавить….
Даже чтение не гарантируется. Тому есть причины, т.к. в этот момент может происходить отрисовка/обработка слота/что-нибудь ещё. Ну а thread-safe вызывает накладные расходы.
Для gui-приложений это нормальное требование, при этом выносить тяжёлую обработку в другой поток ты по-прежнему можешь, как и возвращать результат в основной поток через сигналы-слоты. Только connect нужно делать особый, указывая, что обмен идёт между разными нитями.
admin Reply:
2 марта, 2010 at 16:23
Ну чтение врятли приведет к падению а так да, накладные расходы конечно будут… Но их там и так немало, как мы знаем)