![](https://habrastorage.org/getpro/habr/post_images/af0/689/bb5/af0689bb517e5b715fdd76e84e8760bf.png)
В рамках недели ботов для браузерных игр.
Эта неделя богата на статьи о ботах для бразуерных игр.
Во всех статьях для управления ботом использовался AutoIT. Это простой и хороший способ чтобы начать делать бота «в лоб», он не требует никаких знаний об игре, кроме как знания правил и графического интерфейса. Для серверной части игры такой бот вообще будет выглядеть как обычный пользователь, если не брать во внимание невероятную усидчивость и производительность такого игрока.
Но этот способ требует распознавания мира, что добавляет много ошибок, а иногда определить необходимое значение вообще не представляется возможным.
В первой статье я наткнулся на комментарий со ссылкой на пост Adobe Profiler Fail, что побудило меня исследовать эту возможность в целях автоматизации действий в Flash-играх.
Выбор жертвы
Первым делом я опробовал этот способ на игре Diamond Dash в Google+, которая сразу же покорно согласилась выполнять все мои желания. Для статьи на Хабре я попытался выбрать какую-нибудь другую из представленных игр в Google+, но часть из них требует понимания правил, еще немного игр на JavaScript, а оставшиеся простые на Flash оказались более устойчивы и требуют значительной предварительной подготовки. О внедрении в последние я расскажу в отдельной статье, когда подготовлю необходимые инструменты.
Поэтому препарировать я буду снова Diamond Dash. За одно сравним полученные результаты с результатами из первой статьи.
Идея
Очевидным решением проблемы с распознаванием мира является внедрение во внутренности игры с целью получения точных данных о состоянии мира. В голову приходят два способа:
- Декомпилировать, внедрить свой код и собрать обратно
- Каким-то другим способом получить доступ к объектам
Если кто-то пытался скомпилировать декомпилированный код, то он отлично знает что сделать это не так уж просто, придётся долго повозиться восстанавливая участки кода с которыми декомпилятор не справился. А если этого когда несколько тысяч строк, то задача совершенно себя не оправдывает.
А затем надо решить проблему запуска нашего SWF-файла в доверенном домене, чтобы он без труда общался с сервером.
Поэтому мы пойдем вторым путём. Это избавит нас изучения всего кода и позволит сосредоточиться только на самом главном.
Prerequisites
Для выполнения нашей задачи потребуются:
- Debug-версия Flash Player
- De Monster Debugger (не обязательно)
- Любой ActionScript3 декомпилятор
Исследования
Начнём исследование игры с её подключения к De Monster Debugger. Это очень интересный инструмент для отладки Flash. Даже если вы не собираетесь ставить рекорды в браузерных играх рекомендую его поставить хотя бы ради того, чтобы поиграть в небольшой квест, демонстрирующий возможности этого отладчика.
После установки Монстра и прохождения квеста напишем небольшой кусок кода:
![](https://habrastorage.org/getpro/habr/post_images/a46/903/0fb/a469030fb2326ef01a39f4a497ec18ec.png)
MonsterConnector.as
Полученный SWF файл будет служить обёрткой для всех загружаемых swf и подключать их к отладчику, после того как мы пропишем путь к нему в %USERPROFILE%\mm.cfg:
PreloadSWF=c:\temp\MonsterConnector.swf
Перезапускаем браузер и идём играть в Diamond Dash. Как только игра загрузится наш прелоадер подключит её к Monster Debugger:
![](https://habrastorage.org/getpro/habr/post_images/409/55b/868/40955b868f8c1672c6f3dcebc3eaf66a.png)
Можно немного поковыряться в игре через сам дебаггер, чтобы немного понять её устройство. В дальнейшем это избавит от анализа всего исходного кода. В нашем случае проинспектировав игровое поле можно достаточно быстро узнать что класс кубика называется Brick (неожиданно, правда?).
Теперь нам всё же понадобится декомпилировать код, но не с целью его модификации, а лишь затем чтобы немного изучить.
Для этих целей отлично подойдёт Sothink SWF Decompiler, либо можно воспользоваться бесплатным ASDec (он пока в стадии ранней беты, но зато позволяет править байткод, что в исследованиях кому-то тоже может пригодиться).
Первым делом найдём что происходит при клике мыши. Поискав по тексту MouseEvent натыкаемся на уже известный нам класс Brick, который реагирует на клик мышью:
![](https://habrastorage.org/getpro/habr/post_images/704/7b5/433/7047b54339d577fc3184ed6e0bfaf148.png)
И генерирует событие EVENT_BRICK_CRASH у GameManager.instance со своими координатами:
![](https://habrastorage.org/getpro/habr/post_images/e5d/c73/d28/e5dc73d283f58c04ac1501c6bfe1da18.png)
Как видим, у класса GameManager есть публичное статичное свойство instance. Это нам на руку — достаточно найти этот класс и можно начинать генерировать поддельные клики.
Находим класс:
gameManager = loader.applicationDomain.getDefinition('pl.fabrykagier.collapse::GameManager');
Бегло просмотрев код класса GameManager находим событие EVENT_START_GAME. Подписавшись на него мы будем знать когда можно начинать кликать.
А теперь поиграем
Монстр нам больше не нужен, дальше мы будем работать самостоятельно. Можно удалить код Монстра и добавить проверку на адрес загружаемой swf, чтобы не пытаться играть в баннеры на Хабре :)
if (loader.loaderURL != 'https://secure.f**tprint.net/w**ga/g**gle_test/Diamond.swf') return;
А теперь попробуем генерировать клики по всем кубикам подряд:
![](https://habrastorage.org/getpro/habr/post_images/928/70c/e59/92870ce591b888a5c08f50895253dcc9.png)
MonsterConnector.as
Перезапускаем браузер, запускаем игру…
![](https://habrastorage.org/getpro/habr/post_images/b80/fd7/2f3/b80fd72f3ab0431b887395ee936e787f.png)
Работает!
Но хороших результатов нам таким образом не добиться, а кристаллы, которые появляются при безошибочных кликах, мы так вообще никогда не увидим.
Теперь пора определить состояние поля и действовать более осмысленно.
Немного исследовав код мы найдём что все кубики хранятся в свойстве grid объекта типа GameArea, но беда в том, что это свойство приватно и получить к нему доступ напрямую мы не можем. Сам класс GameArea не предоставляет нам никаких публичных методов для получения положения кубиков. В нём же есть функция findBiggestGroup для определения самой большой группы, которая нам могла бы пригодиться, но она как на зло тоже оказалась приватной.
Но в начале статьи мы уже видели класс Brick в Монстре. Значит, мы можем просто найти все эти кубики в сцене.
Я сделал это просто рекурсивно перебрав все видимые объекты в сцене. Возможно, есть способ проще.
![](https://habrastorage.org/getpro/habr/post_images/ae5/c7b/afd/ae5c7bafd5f5c0f48aabe8097a9ba25e.png)
Ну а теперь копируем найденную ранее функцию findBiggestGroup, адаптировав её под наше представление данных. Любители алгоритмов и ненавистники рекурсивных функций могут написать свою :)
Очередной запуск…
![](https://habrastorage.org/getpro/habr/post_images/529/d6b/972/529d6b9728a8665c2f8b0c62daaba6ef.png)
Всё поле было безошибочно «разобрано», за что нам надавали кучу кристаллов, которые мы забыли собрать. Не смотря на это мы получили неплохой результат, переплюнув бота на AutoIT!
![](https://habrastorage.org/getpro/habr/post_images/3bf/c9a/aef/3bfc9aaefb25f7eada5f59ef0943ab4e.png)
Кристаллы это те же самые экземпляры Brick, но со значением color = 21. Будем кликать по ним сразу же при обнаружении.
![](https://habrastorage.org/getpro/habr/post_images/291/f82/77b/291f8277b461c0b9d69844c91b60e1f3.png)
Запускаем еще раз, ждём минуту, и…
![](https://habrastorage.org/getpro/habr/post_images/4d8/bf3/e4d/4d8bf3e4d4def82e531587673051e446.png)
На самом деле результат должен быть чуть меньше, потому как бот продолжает играть когда игра уже закончилась и идёт подсчёт бонусных очков.
Исправить это можно в пару-тройку строк кода. Разобраться с этим предлагаю читателю самостоятельно.
Финальная версия MonsterConnector.as
Итоги
Внедриться в Flash приложение достаточно просто, особенно если разработчик любезно предоставил нам ссылку на экземпляр важнейшего класса.
Не сильно сложнее найти объекты в сцене зная лишь название класса, а затем использовать их свойства, если разработчик не отделил
В следующий раз я постараюсь рассказать что можно сделать если разработчик был более предусмотрителен и не оставил публичных свойств и функций, либо просто строго придерживается модели MVC, лишив нас возможности найти данные в сцене.