Атака клонов: Современные технологии ботовождения

«Сейчас вы напишите самую сложную в своей жизни программу, которая будет просто складывать два числа»
Рыжова Ирина Михайловна

Интеллектуально-азартные онлайн-игры — лакомый кусок для ботоводов. Даже если разработчики игрового софта тратят большие деньги на отлов ботов, — как в онлайн покер-румах, например — всё равно велика вероятность наткнуться на «умного бота», игра с которым будет в одни ворота. Особенно, если бот абсолютное неуязвим… Ибо никакие деньги не защитят систему, через которую проходят большие деньги.

44dca73eae9f45debb57d564bd93ca7c.jpg
Рис. 1. Ботоводы наступают


Интеллектуально-азартные онлайн-игры — небезопасны. В особенности небезопасны те из них, где вращаются живые деньги. Их небезопасность проявляется прежде всего в том, что невозможно знать наверняка, с кем играешь: с живым человеком, или же с — ботом. Во втором случае игра будет в одни ворота. В играх «на интерес», — шахматы и шашки на маил.ру, к примеру, — владельцы онлайн-игр смотрят на ботовождение сквозь пальцы. В многопользовательских онлайн-играх, где в какой-то степени фигурируют живые деньги, отлову ботов уделяется несколько большее внимание. Однако даже если подходить к пресечению ботоводства в высшей степени серьёзно — как это например сделано в онлайн покер-румах — всё равно велика вероятность наткнуться на бота.

Разработчики софта для игры в покер тратят большие деньги на разоблачение ботов, и тем не менее, ботоводство в онлайн-покере процветает. Ибо никакие деньги не смогут защитить систему, через которую проходят большие деньги. Ботоводы и ловцы ботов, — анализируя уловки оппонентов и принимая соответствующие контрмеры, — попеременно одерживают победу. Кто-то может думать, что битве между ними никогда не будет конца. Однако, существует схема безопасного ботовождения, перед которой ловцы ботов — пасуют. Она интересна тем, что даже если «борцы за справедливость» будут иметь на руках подробно прокомментированные исходные коды бота, они не смогут установить факт его использования. Реализация данной схемы — мероприятие дорогостоящее, однако поскольку потенциальная выгода велика, схема вполне актуальна.

# Автобиографические заметки

Мой двоюродный дядя — рыбак, он и меня к рыбалке приобщил. Он — виртуозный рыбак со стажем, окончивший ТУСУР. Дядя начинал рыбачить на военном приборостроительном заводе РОТОР (Алтайский край, Барнаул), участвуя в разработке подводных ракет спирального наведения на «объекты-невидимки» (не помню, как это по-умному называется). А желание пойти в ТУСУР у него возникло, когда, дядя в очередной раз отправляясь копать червей, в специально приготовленную для этого консервную банку, наткнулся на своего гения-знакомого, который не долго думая сделал из этой консервной банки радио. Это так впечатлило дядю, что он захотел стать электронщиком. После рыбалки на РОТОРе, он переквалифицировался на производство электроудочек, которые даже патентовать не надо было, поскольку дядя использовал в них мало кому доступные запчасти-обломки ЭВМ (тех самых многокомнатных ЭВМ прошлого тысячелетия); у него был доступ к этой технике, т.к. он после РОТОРа что-то околоЭВМное инженерил. У меня до сих пор на веранде ещё какие-то запчасти этих динозавров остались, хотя я и прореживаю регулярно их завалы. В прошлом году наконец-то выкинул ведро давно протухшего винегрета из микросхем (ЛА3, Триггеры, мультиплексоры, таймеры, буфера и т.д.). После электроудочек мой дядя переквалифицировался в обычного рыбака. Просто стал ходить на рыбалку. Вот на этих рыбалках он меня и начал приобщать к полезным делам за компьютером. Одна из самых запоминающихся рыбалок для меня — это разработка программно-аппаратного комплекса для невидимой рыбалки в онлайн-покер-румах (бот для покера, в простонародии). А в покер-румах, как известно, самый чуткий рыбнадзор, поэтому просто программная эмуляция, даже на уровне драйверов, может быть отслежена — со всеми вытекающими. Поэтому невидимый покер-бот — это довольно-таки дорогая игрушка, но с помощью неё в покер-румах очень много рыбы наловить можно (подробнее см. «Рыбацкие байки автобиографического характера»).

Три условия неуязвимого ботовождения


Любой бот по сути своей делает всего три вещи: фотографирует, анализирует и реагирует. Во-первых, бот фотографирует текущее состояние игры — либо скриншотом экрана, либо перехватом сетевого трафика. Во-вторых, бот принимает решение, как поступать в сложившихся обстоятельствах, — иногда прибегая к помощи стороннего софта (например, в случае с шахматами, он может взаимодействовать с каким-нибудь шахматным движком). Наконец в-третьих, бот эмулирует взаимодействие пользователя с клавиатурой и мышью. Далее этот троичный цикл, — который ловцы ботов стремятся отследить и сломать, — повторяется. Соответственно битва ботоводов и «борцов за справедливость» разворачивается на трёх фронтах. У этой битвы продолжительная и захватывающая история, однако мы остановимся лишь на заключительном её поединке, в котором «борцы за справедливость», перед лицом абсолютно неуязвимого бота, — потерпели безоговорочное фиаско.

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

# Таргетированный руткит

Это одна из передовых методик разоблачения покерных ботов. Она, скажем так не совсем законная, поэтому когда разработчиков покерного софта спрашивают, какими механизмами для обнаружения ботов те пользуются, они отвечают что-то вроде: «Мы не разглашаем эту информацию, чтобы не давать ботоводам пищу для размышлений о создании более продвинутых ботов». Именно из-за своих «не совсем законных действий» покерный софт иногда натыкается на «ложные срабатывания» антивирусов. Однако антивирусы, принимающие покерный софт за трояна, бэкдор или руткит — не всегда так уж и далеки от истины.

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

Для обхода внешнего периметра защиты, клиентский софт, подгружающий таргетированный руткит, пользуется элементами стеганографии: «распыляет» код либо по графическим файлам, либо по невостребованным частям протокола передачи данных. Именно поэтому ботоводы, — перехватывающие трафик клиентского софта, — обнаруживают, что там всё происходит «как-то не очень стандартизовано».

Одна из наиболее известных мер противостояния ботам — незначительное изменение графических элементов. Однако мало кому известно, что при подгрузке с сервера такой «незначительно изменённой графики», иногда вместе с ней подгружается «пасхальное яйцо» в виде шеллкод руткита. Таким образом загрузить подобный код на компьютер, — что уже само по себе не очень законно, — совсем не сложно. Однако ещё менее законно скрытое выполнение этого кода, использующее механизмы самомодификации, криптования и полиморфизма — откровенно вирусных техник.

Также стоит отметить, что таргетированный руткит, это шеллкод одноразового пользования. Поэтому два гипотетических человека, заподозренные в ботоводстве, получат два абсолютно непохожих шеллкода. Кроме того, инфицирование таргетинговым руткитом происходит не в автоматическом, а полуавтоматическом режиме, а логика его работы программируется в зависимости от того, какая информация требуется покер-руму о человеке, заподозренном в ботоводстве. Отсюда и название — «таргетированный».

Современный покерный софт имеет в себе элементы руткита, и т.о. внедряется в систему настолько глубоко, что может отслеживать любые попытки эмуляции игры. Решение заключается в том, чтобы запускать бота не на рабочей станции, с которой ведётся игра, а на отдельном компьютере — эмуляторе. Тогда, при соблюдении трёх условий параноидального характера, у покерного софта не будет шансов распознать факт эмуляции: 1) рабочая станция и эмулятор не должны иметь возможности связываться по сети; 2) для фотографирования текущего состояния игры следует использовать аналоговый выход видеокарты, — соединённый с «компьютером-скриннером» посредством платы видеозахвата; 3) для эмуляции клавиатуры и мыши следует использовать программно-аппаратную примочку, вход которой подключен к компьютеру-эмулятору, а выход — к двум PS/2 рабочей станции, с которой ведётся игра. Именно аналоговый видеовыход и именно PS/2 — чтобы покерный софт не знал, что к компьютеру подключено какое-то дополнительное оборудование.

Биометрия и технометрия


Реализуя эмуляцию клавиатуры и мыши, следует учесть соответствующие биометрические и технометрические обстоятельства, — которые также могут отслеживаться покерным софтом. Что касается биометрии, то при эмуляции клавиатуры и мыши следует позаботиться, чтобы эмулируемые телодвижения были похожи на правду. При этом следует учесть, что, во-первых, живой человек не может играть на стабильно высоком темпе в течение долгого времени сразу за 16-ю столами одновременно — в особенности на восьмидюймовом мониторе. Во-вторых, живой человек не может играть 28 часов в сутки, 6 дней в неделю. Наконец в-третьих, телодвижения живого человека могут меняться — в зависимости оттого, сколько времени он провёл за компьютером. Для эмуляции правдоподобных биометрических обстоятельств потребуется: 1) «PS/2-сниффер» — программно-аппаратная примочка, которая будет слушать поток данных, посылаемых клавиатурой и мышью на порт PS/2, и сохранять всё услышанное в отдельный файл, который в последующем будет использоваться для эмуляции правдоподобных биометрических обстоятельств; 2) живой человек, который реально поиграет в онлайн-покер столько времени, на сколько планируется включать бота — именно его действия и будет фиксировать «PS/2-сниффер».

Что касается технометрии, то у каждой клавиатуры и мыши есть свой уникальный «почерк». Его можно отследить, анализируя на низком уровне сырой поток сигналов, которые поступают от них на компьютер. Поэтому если в ботовождении участвуют сразу несколько рабочих станций, то при подготовке к биометрической эмуляции, для каждой из них следует: 1) организовать индивидуальную прослушку, — с разными клавиатурами и разными мышами, 2) посадить за каждую рабочую станцию разных людей, чьи действия будут фиксироваться «PS/2-сниффером». Необходимость второго пункта обусловлена тем фактом, что каждый человек пользуется мышью и клавиатурой индивидуальным образом. При достаточно обширном статистическом материале, можно с достаточной степенью достоверности определить, кто сидит за компьютером, — даже если пользователь не аутентифицирован.

# Технометрия по-дедовски

В далёком советском прошлом, когда в ходу были печатные машинки и в стране была жёсткая цензура, спецслужбы ставили некоторые из печатных машинок «на учёт»: фиксировали физические особенности литерных рычагов, — образно говоря, «снимали у печатных машинок отпечатки пальцев». В результате, когда появлялся какой-то «неугодный текст», КГБ могло отследить, на какой из машинок он был напечатан. Более того, уже тогда спецслужбы могли восстанавливать текст буквально из пепла. В числе прочих с их этой способностью столкнулись последователи Харе Кришна, чьи книги — в особенности «Бхагавад-гита» и «Шримад-Бхагаватам» — в советские времена были под строжайшим запретом. Поэтому в целях конспирации писатели-подпольщики, после того как рукопись переводилась в печатный текст, — не просто разрывали её на мелкие кусочки и сжигали, а разбрасывали пепел по каплям в разных местах: частично спускали в туалет, частично высыпали в одну помойку, частично в другую, третью, десятую. Трудно себе представить, какими возможностями обладают спецслужбы сейчас, когда научно-технический прогресс, по сравнению с советскими временами, ушёл далеко вперёд.

Итак, подготовка к эмуляции с правдоподобными биометрическими и технометрическими параметрами произведена. Теперь осталось запомнить, какая клавиатура с мышью, какому пользователю соответствуют — и никогда не нарушать этого соответствия. Игнорирование биометрии и технометрии или неправдоподобное их эмулирование, может привести к блокировке аккаунта без объяснения причин. При соблюдении же всех мер предосторожности, — какими бы параноидальными они не казались, — можно рассчитывать на неуязвимое ботовождение.

Элементы неуязвимого бота


5bfced4eb7ce49dcb4216ab51eb7edbd.jpg
Рис. 2. Неуязвимый бот

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

1. Рабочая станция (с выделенным IP или качественным PROXY), где одновременно запущено 9 покерных комнат. На ней должна быть видеокарта с аналоговым видеовыходом высокой чёткости и большой монитор, — поскольку много комнат на маленьком мониторе выглядят подозрительно. Экономически более рентабельно использовать не одну рабочую станцию –, а сразу порядка десяти. В таком случае бот можно будет запустить одновременно сразу в 90 комнат.

2. Скриннер — мощный компьютер с платой видеозахвата, которая соединена через видео-сплиттер со всеми рабочими станциями. Задача скриннера — распознавать принятые сигналы и переводить их в структурный вид. Эти данные в последующем будут использованы для 1) записи в базу данных, 2) аналитики и 3) принятия решений к действию.

3. Аналитик — мощный компьютер, работающий по принципу метео-сервера, но вместо природных явлений накапливающий все возможные покерные данные — теллсы. На самом деле в компьютерном покере теллсов гораздо больше, чем в живом. Но беда в том, что без вспомогательного софта их отследить практически невозможно. А использование такого софта всячески пресекается — можно бан получить, если пользоваться им на рабочей станции.

4. Шпион — отдельные аккаунты, с которых игра не ведётся, а ведётся просто последовательный сбор всей доступной информации со всех доступных комнат. Шпионов должно быть несколько, потому как если параллельно с нашей игрой один и тот же аккаунт логинится, а с её концом — разлогинивается, это может кидать планомерную монету в копилку подозрений, и когда копилка эта копилка переполнится, нам просто без объяснения причин закроют аккаунт. «Шпион» должен работать по тому же принципу, что и «рабочая станция». Может показаться, что здесь достаточно программного скрина и программной эмуляции мыши с клавиатурой. Однако, поскольку покерный софт потенциально может к железу привязаться и последующие аккаунты баннить, для шпиона следует сделать аппаратный скрин и эмуляцию — также как и для рабочей станции.

5. Эмулятор — компьютер, мощность которого не принципиальна. Он соединён с несколькими сплиттерами для мыши и клавиатуры. От этих сплиттеров идут провода на входы PS/2 для мыши и клавиатуры на каждую из рабочих станций и шпионов. Этот компьютер получает от аналитика команды и передаёт их рабочим станциям. Учитывая при этом аппаратные особенности мыши и клавиатуры, и физиологические особенности поведения человека, работающего за ними (биометрию и технометрию).

6. Два живых наблюдателя. Это уже не компьютеры, а именно живые люди. Они нужны, потому что разработчики покерного софта не дремлют, и в любой момент могут запустить какой-нибудь уникальный тест, — чтобы выявить ботоводов. Одного человека недостаточно, — поскольку он может уйти по нужде или попудрить носик, а в это время какая-то нештатная ситуация возникнет. Бывают ситуации, где всё решают секунды. Поэтому важно, чтобы рядом с машинами гарантированно кто-то всегда находился. Чтобы когда «загорится красная кнопка» (когда аналитик встретит незнакомое поведение), человек мог подсказать ему, что сделать (в чате что-то написать или окошко какое-то закрыть). Причём ни в коем случае нельзя подходить непосредственно к рабочей станции, — чтобы не сломать соответствующую ей поведенческую модель клавиатуры и мыши. Действия необходимо совершать только через «командный пункт».

7. Командный пункт — компьютер, мощность которого также не принципиальна. Он должен быть соединён только с эмулятором. Это именно та машина, у которой сидят два живых наблюдателя и через которую они при необходимости вносят необходимые корректировки в поведение бота.

Последние штрихи


Итак, это 7 элементов неуязвимого бота. При их подключении следует учесть, что скриннер, аналитик и эмулятор обмениваются данными по сети. Взаимодействие же с рабочими станциями и шпионами происходит строго через аналоговый выход видеокарты и PS/2. Причём, эти компьютеры должны быть оторваны физически ото всех остальных. Не может быть и речи связи между ними по сети. Ввиду сложности такого программно-аппаратного комплекса, важно также позаботиться о том, чтобы минимизировать ошибки связанные с человеческим фактором. Для этого эмулятор помимо всего прочего заботится о запуске всего необходимого софта на рабочих станциях и на шпионе. Скриннер, аналитик и эмулятор должны быть в боевой готовности сразу после включения — соответствующий софт просто в автозагрузке должен быть.
67d5f9189f51483ab7ecbbfed5f29e5b.jpg
Рис. 3. Новая надежда

Описанная схема ботовождения, даже при поверхностном ознакомлении с ней — не для слабонервных. А ведь выше приведено, хоть и подробное, но всё-таки — поверхностное её описание. Для её реализации нужно обладать не дюжей технической квалификацией, как в программировании, так и в электронике. При программировании разработчику потребуются знания из таких областей, как дискретная математика, сплайновая аппроксимация, вейвлет-преобразования, нейронные сети, конечные автоматы, нечёткая логика, многопоточное программирование, цифровая обработка сигналов. При реализации аппаратной части бота, разработчику потребуются знания из таких областей, как микропроцессорные системы, цифровая и микропроцессорная техника, работа с микроконтроллерами и ПЛИС, основы электротехники, низкоуровневое программирование драйверов, архитектура ОС, архитектура процессора.

# Кто кого перехучит

Общеизвестное вялотекущее противостояние ботоводов и их ловцов — это своеобразная игра в прятки, развивающаяся по принципу «кто кого перехучит». Боты средней продвинутости, — перехватывая соответствующие функции, которыми пользуется покерный софт, — пытаются скрыть факт фотографирования экрана, эмуляции мыши и использования запрещённых программ. А те в свою очередь, стараются обнаружить факт подобного перехвата. В процессе этой увлекательной игры, — начинающейся с функций вроде SetWindowsHookEx, CreateToolHelpSnapshot32, EnumProcessModules, — ботоводы и их ловцы закапываются всё глубже и глубже в ядро операционной системы. Такое погружение в ОС происходит ни без юмора. Например, несколько лет назад случился казус: спустя пару недель после публикации исходников одного покерного бота, PokerStars ввёл санкции для игроков, на чьих компьютерах была обнаружена среда Visual Studio.

Итак, это схема безопасного ботовождения. Как было сказано вначале, она интересна тем, что даже если «борцы за справедливость» будут иметь на руках подробно прокомментированные исходные коды бота, они не смогут установить факт его использования. Тогда как обычного бота, — без функции невидимости, — «борцы за справедливость» могут отследить, даже не имея его исходников. Современные технологии, в частности руткиты, — на которые разработчики защиты покерного софта возлагают большие надежды, — позволяют глубоко окопаться в операционной системе, и т.о. действовать по принципу «высоко сижу, далеко гляжу». С развитием руткитов и других защитных технологий, у «борцов за справедливость» появилась новая надежда на безоговорочную победу. Однако вышеприведённая схема безопасного ботовождения, сводит все эти надежды на нет. Реализация данной схемы — мероприятие дорогостоящее, однако поскольку потенциальная выгода велика, схема вполне актуальна. Так что имеет смысл дерзнуть.


Сказано — сделано. Дерзнём! Вот только стоит ли для этого тратить время на остаток статьи? Первоклассному IT-инженеру, чья принадлежность к высшей лиге X-сцены подтверждена чем-то более весомым, нежели просто красивая печать в дипломе, — определённо не стоит. В его X-арсенале уже достаточно мистических приёмов, чтобы без чьей либо посторонней помощи «отомстить» за старых ботов, которым «светлая сторона силы» некогда перекрыла кислород.

Продвинутым девелоперам, чья сопричастность с X-сценой обусловлена лишь их виртуозным владением C++ и ассемблером, время тратить также не стоит, но уже по иной причине — не потянут. Степень подробности излагаемого материала для них недостаточна, — поскольку в этой статье намеренно оставлены без внимания не только многие тривиальные задачи программирования (с ними продвинутый девелопер справляется на раз-два-три), но также и некоторые принципиальные инженерные концепции. Что же касается скрипткиддис, которые даже разговорным C++ и ассемблером не владеют — то о пользе этой статьи для них, вообще речи нет.

Однако инженер средней руки, — уже вышедший из подросткового девелоперства (развивший мало-мальски инженерную смекалку), но ещё не вступивший в пору взрослого инжиниринга, — найдёт здесь интересные идеи, которые помогут ему реализовать неуязвимого бота. Постольку поскольку он уже способен находить простые решения для кажущихся сложными задач, используя доступные ресурсы. А это именно та мистическая сила, которая ценится на X-сцене, — как на светлой, так и на тёмной её стороне. Умение владеть ею, как раз и отличает инженера от девелопера. Итак, зададим «борцам за справедливость» жару, показав, на что способна тёмная сторона силы.


23f7c351633b4a58b944e3925caffe81.jpg
Рис. 4. Империя наносит ответный удар

Хорошая новость: самое сложное уже позади. Стратегический план действий, — по совместительству являющийся неформальной постановкой задачи, — готов. Теперь его осталось только слегка конкретизировать и детализировать — описать в тактических действиях. На этапе детализации самое важное — хладнокровно пройтись «от общего к частному», и т.о. сформировать каркас будущего проекта. При этом, не вдаваясь в детали реализации конкретных функциональных узлов, а просто фиксируя их. Об их детализации позаботимся позже, — когда каркас уже будет полностью готов.

# Самая сложная программа

Хорошая новость в том, что разработка неуязвимого бота — это не самый сложный инженерный проект. Потому что самая сложная железка и самая сложная программа, которые только доводилось создавать инженеру — это его самая первая железка и самая первая программа. В связи с этим вспоминается далёкий 99-й год. Когда в 10-м классе, на самом первом уроке информатики, Рыжова Ирина Михайловна, моя первая учительница программирования, сказала: «Сейчас вы напишите самую сложную в своей жизни программу, которая будет просто складывать два числа». Это действительно была самая сложная программа, потому что на тот момент о языках программирования и об их IDE мы не имели ровным счётом никакого представления.

Для большей наглядности этапа детализации, воспользуемся синтаксисом языка Форт (не путать с Фортраном). Это уникальный язык, который естественным образом располагает к рациональному движению инженерной мысли. Прелесть такого «Форт-заимствованного» подхода в том, что все концептуальные недочёты проекта, выявляются ещё на берегу, а не в середине его реализации, — что с самого начала способствует качественной разработке. Без необходимости последующего рефакторинга. Итак, каркас «от общего к частному»:

Каркас «от общего к частному»
: КОМАНДНЫЙ-ПУНКТ
  НОВЫЙ-ТРЕД КОНСОЛЬ-УПРАВЛЕНИЯ
  НОВЫЙ-ТРЕД МОНИТОР-СОСТОЯНИЯ
  НОВЫЙ-ТРЕД ОРУДИЯ-К-БОЮ
  ЖДАТЬ-ЗАВЕРШЕНИЯ
;

: ОРУДИЯ-К-БОЮ
  НОВЫЙ-ТРЕД !!!СКРИННЕР!!!
  НОВЫЙ-ТРЕД !!!АНАЛИТИК!!!
  НОВЫЙ-ТРЕД !!!ЭМУЛЯТОР!!!
  НОВЫЙ-ТРЕД ШПИОНЫ
  НОВЫЙ-ТРЕД РАБОЧИЕ-СТАНЦИИ
;

: МОНИТОР-СОСТОЯНИЯ
  ЛОВИТЬ ФОРСМАЖОР-ВСЕХ-ТРЕДОВ
  ПЕРЕДАТЬ-НА КОНСОЛЬ-УПРАВЛЕНИЯ
  ПОВТОРЯТЬ-ДО-ЗАВЕРШЕНИЯ
;

: КОНСОЛЬ-УПРАВЛЕНИЯ
  СООБЩИТЬ-О ФОРСМАЖОР-ВСЕХ-ТРЕДОВ
  РЕАГИРОВАТЬ-НА КОМАНДЫ-ОПЕРАТОРА
  ПОВТОРЯТЬ-ДО-ЗАВЕРШЕНИЯ
;

: !!!СКРИННЕР!!!
  ДЛЯ-КАЖДОЙ-РАБОЧЕЙ-СТАНЦИИ ТЕКУЩИЙ-СКРИН
  ДЛЯ-КАЖДОГО-ШПИОНА ТЕКУЩИЙ-СКРИН
  ПОВТОРЯТЬ-ДО-ЗАВЕРШЕНИЯ
;

: ТЕКУЩИЙ-СКРИН
  ПЕРЕКЛЮЧИТЬСЯ-НА-НУЖНЫЙ-КАНАЛ
  СНЯТЬ-СКРИН-С-ПЛАТЫ-ВИДЕО-ЗАХВАТА
  РАСПОЗНАТЬ-ИГРОВОЕ-ПОЛЕ
;

: РАСПОЗНАТЬ-ИГРОВОЕ-ПОЛЕ
  ДЛЯ-КАЖДОГО-ИГРОКА РАСПОЗНАТЬ-ИГРОКА
  РАСПОЗНАТЬ-НЕЙРОСЕТЬЮ ОТКРЫТЫЕ-КАРТЫ
  РАСПОЗНАТЬ-НЕЙРОСЕТЬЮ НЕИЗМЕННОСТЬ-СТАТИКИ
  РАСПОЗНАТЬ-НЕЙРОСЕТЬЮ СООБЩЕНИЯ-ЧАТА
  РАСПОЗНАТЬ-НЕЙРОСЕТЬЮ ВСЁ-ОСТАЛЬНОЕ
  СОХРАНИТЬ-ВСЁ-В-СТРУКТУРУ
  ПЕРЕДАТЬ-НА ОРАКУЛ
;

: !!!АНАЛИТИК!!!
  ВЗЯТЬ-ИЗ-БД ТЕКУЩЕЕ-СОСТОЯНИЕ-ИГРЫ
  ВЗЯТЬ-ИЗ-БД ИСТОРИЮ-ИГРЫ
  ВЗЯТЬ-ИЗ-БД ПОХОЖИЕ-СИТУАЦИИ
  ВЗЯТЬ-ИЗ-БД КАК-РАЗВИВАЛИСЬ-СОБЫТИЯ
  ОЦЕНИТЬ-ВЫГОДУ
  ОПРЕДЕЛИТЬСЯ-С-ДЕЙСТВИЕМ
  СООБЩИТЬ-О-РЕШЕНИИ-ЭМУЛЯТОРУ
  ПОВТОРЯТЬ-ДО-ЗАВЕРШЕНИЯ
;

: !!!ЭМУЛЯТОР!!!
  ПЕРЕКЛЮЧИТЬСЯ-НА-НУЖНЫЙ-КАНАЛ
  ПРИНЯТЬ-КОМАНДУ-АНАЛИТИКА
  ЭМУЛИРОВАТЬ-МЫШЬ
  ЭМУЛИРОВАТЬ-КЛАВИАТУРУ
  ПОВТОРЯТЬ-ДО-ЗАВЕРШЕНИЯ
;

: !!!ШПИОН!!!
  ПОДСТРОИТЬ-РАБОЧЕЕ-МЕСТО
  СООБЩИТЬ-О-ГОТОВНОСТИ-СКРИННЕРУ
;

: !!!РАБОЧАЯ-СТАНЦИЯ!!!
  ПОДСТРОИТЬ-РАБОЧЕЕ-МЕСТО
  СООБЩИТЬ-О-ГОТОВНОСТИ-СКРИННЕРУ
;

: ПОДСТРОИТЬ-РАБОЧЕЕ-МЕСТО
  ЗАПУСТИТЬ-ПОКЕРНЫЙ-КЛИЕНТ
  ЗАЙТИ-В-ДЕВЯТЬ-КОМНАТ
  СМЕНИТЬ-КОМНАТУ-ПРИ-НЕОБХОДИМОСТИ
;

Ещё одно преимущество использования синтаксиса языка Форт при составлении общего каркаса проекта — это возможность сразу же скомпилировать написанный текст. Потому что этот текст уже сам по себе является программой. В принципе, если реализовать все недостающие функциональные узлы средствами языка Форт и добавить их к вышеприведённому каркасу, то получится тот самый неуязвимый бот, над которым мы трудимся. Получится весьма лаконично. Тем не менее, приведённые ниже фрагменты кода, будут написаны на более традиционных языках — C++ и ассемблере. «Форт-заимствованное» представление в данном случае интересно лишь для наглядности общего каркаса проекта. Итак, «хладнокровно от общего к частному» прошлись, теперь рассмотрим подробнее детали реализации некоторых функциональных узлов бота.

Невидимое фотографирование


Здесь у нас три принципиальные задачи: 1) найти приемлемое аппаратное решение, 2) разработать структуру данных, которые будут считываться с фотографий, 3) реализовать функцию распознавания. Результатом этапа фотографирования должна стать полная и неизбыточная структура данных, опираясь на которую автомат логики сможет однозначно идентифицировать текущее состояние игры — за как можно меньшее число подряд идущих снимков.

1. Что касается аппаратного решения, то идеальный вариант (с экономической точки зрения и с точки зрения простоты последующего программирования) — это чтобы все видеосигналы сходились на один видео-сплиттер, который можно было бы переключать с компьютера. В таком случае задачу «скриннера» сможет выполнять один компьютер с одной платой видеозахвата. Для каждого шпиона и рабочей станции в этом случае надо будет завести по одной папке, — туда будут сохраняться соответствующие скрины. Если этот вариант реализовать не получается (в силу недоступности соответствующего оборудования и/или в силу недостаточной технической квалификации для адаптации существующего оборудования под собственные нужды), то можно использовать несколько компьютеров, объединённых в локальную сеть, в каждом из которых будет по несколько плат видео-захвата — по одной на каждого «шпиона» и «рабочую станцию». В таком случае нужно будет либо в общую сетевую папку все отскриненные битмапы складывать, либо на сокетах клиент-серверное приложение написать, которое будет все битмапы в одном месте собирать. Для загрузки битмапов, с целью их последующего распознавания, можно использовать следующий код:

Загрузка битмапов
void LoadRamaFromFile(LPCSTR lpcszFileName, HDC *lphdcRama)
{
  HANDLE hBitmap = LoadImage(0, lpcszFileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
  *lphdcRama = CreateCompatibleDC(NULL); // Подготовить контекст для снимка
  SelectObject(*lphdcRama, hBitmap);   // Поместить снимок в контекст
  DeleteObject(hBitmap);         // Освободить память
}

2. Что касается структуры данных, то она может быть такой:

Структура данных
// Раунды
typedef enum ROUND
{
  RES_WAITINGBLINDS, // ожидание блайндов (служебный раунд)
  RES_FLOPHIDDEN,  // начался флоп, но карты ещё не вскрыты ()
  PREFLOP,      // префлоп
  FLOP,       // флоп
  TURN,       // терн
  RIVER,       // ривер
  UNKNOWN,      // неизвестный
  ZSTATE
} *LPROUND;

// Возможные действия игроков                      //
typedef enum ACTION
{
  AC_POSTSB,   // оплатил малый блайнд
  AC_POSTBB,   // оплатил большой блайнд
  AC_POSTSBANDBB,
  AC_CHECK,    // передал право первого слова
  AC_BET,     // сделал первую ставку
  AC_RAISE,    // повысил предыдущую ставку
  AC_CALL,    // принял текущую ставку
  AC_FOLD,    // сбросил карты
  AC_ALLIN,    // поставил всё
  AC_SITOUT,   // не заплатил блайнд и пропустил руку или не сделал ход во время
  AC_MUCK,    // скрыл свои карты
  AC_SHOWCARDS,  // показал свои карты
  AC_NONE,    // пустой кругляк, но игрок в игре (карты при нём)
  AC_TNB,     // терм недоставляющий беспокойства (место свободно)
  AC_ZSTATE
} *LPACTION;

// Информация по одному игроку
typedef struct tagSITA_UVACHA
{
  char  szNickname[STR_SIZE_NICKNAME];  // Псевдоним
  char  szStack[STR_SIZE_STACK];     // Размер стека
  char  szHoleCards[STR_SIZE_HOLECARDS]; // Какие карты на руках (если известны)
  ACTION  action;
} SITA_UVACHA, *LPSITA_UVACHA;

// Информация по всей игре
typedef struct tagRAMA_UVACHA
{
  SITA_UVACHA  sita[MAX_COUNT_SITA];   // Информация по всем игрокам
  DWORD  dwCountSita;           // Количество игроков за столом
  DWORD  dwBUPos;             // Позиция баттона
  char  szPOT[STR_SIZE_POT];       // размер банка
  char  szBoardCards[STR_SIZE_HOLECARDS]; // карты, лежащие на игровом столе
  ROUND  round;              // текущий раунд
} RAMA_UVACHA, *LPRAMA_UVACHA;

////////////////////////////

typedef struct tagKRISHNA_UVACHA
{
  RAMA_UVACHA  rama;
  DWORD    dwFirstSaid;    // первый новый говорящий на текущем снимке
  DWORD    dwLastSaid;     // последний новый говорящий на текущем снимке
  ACTION    last_action;    // последнее действие на текущем снимке
} KRISHNA_UVACHA, *LPKRISHNA_UVACHA;

3. Один из возможных вариантов функции распознавания, — настроить для этого нейронную сеть. Данная тема заслуживает отдельного разговора, поэтому о нейросетевом подходе в этой статье мы говорить не будем. Более простой вариант — привязаться к соответствующим битмапам. Вот как может выглядеть соответствующая структура данных:

Структура данных для распознавания
typedef struct tagSURJA_CRAPH_DATA
{
  BYTE  PATTERN_LETTER[COUNT_LETTERS][SIZE_PATTERN_LETTER];       // рисунки символов
  BYTE  LETTER_CODE[COUNT_LETTERS];                   // коды символов
  BYTE  LETTER_SIZE[COUNT_LETTERS];                   // ширина символов

  BYTE  PATTERN_INSCRIPT[COUNT_INSCRIPTIONS][SIZE_PATTERN_INSCRIPTION]; // рисунки действий игроков
  char  INSCRIPTION_TEXT[COUNT_INSCRIPTIONS][SIZE_INSCRIPTION_TEXT];  // действия игроков
  BYTE  PATTERN_CARD[COUNT_CARDS][SIZE_PATTERN_CARD];          // рисунки карт
  char  CARD_TEXT[COUNT_CARDS][SIZE_CARD_TEXT];             // перечень карт
  BYTE  PATTERN_HOLEHIDDEN[COUNT_HOLEHIDDEN][SIZE_PATTERN_HOLEHIDDEN]; // рисунки скрытых ручных карт
  char  HOLEHIDDEN_TEXT[COUNT_HOLEHIDDEN][SIZE_HOLEHIDDEN_TEXT];    // текстовые обозначения скрытых ручных карт
} SURJA_CRAPH_DATA, *LPSURJA_GRAPH_DATA;

Самый простой способ её заполнения — ручной. Просто делаем снимок экрана, открываем его в фотошопе, активизируем инструмент «пипетка» и переписываем цифры с интересующих нас областей. Лаконичным это решение не назовёшь даже с большой натяжкой. Кроме того, оно имеет существенное ограничение, — даже самые незначительные изменения в графике покерного софта приведут к тому, что бот перестанет работать. Однако если техническая квалификация не позволяет придумать что-то более стоящее, то вот как может выглядеть подготовка к «ручному распознаванию» (здесь приведена только небольшая выборка из кода почти в 2000 строк):

Ручное распознавание
BOOL LoadGraphData()
{
//===================== Сначала обнуляем все шаблоны ================
  memset(m_BramaGraphData.ptRamaCoords,  0,  sizeof(POINT)*COUNT_VARRIOUS_RAMA*COUNT_RAMA_VALUES);
  memset(m_BramaGraphData.ptSitaCoords,  0,  sizeof(POINT)*COUNT_VARRIOUS_RAMA*MAX_COUNT_SITA*COUNT_SITA_VALUES);
  memset(m_BramaGraphData.ptRecognizeSize,0,  sizeof(POINT)*COUNT_VAL_SIZE);
//===================== Параметры 9-стульчатого стола ===============
  m_BramaGraphData.ptRamaCoords[INDEX_RAMA_AT_9_SITA][INDEX_VAL_RAMA_POT].x  = 210;
  m_BramaGraphData.ptRamaCoords[INDEX_RAMA_AT_9_SITA][INDEX_VAL_RAMA_POT].y  = 34;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][0][INDEX_VAL_SITA_NICKNAME].x    = 340;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][0][INDEX_VAL_SITA_NICKNAME].y    = 44;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][1][INDEX_VAL_SITA_NICKNAME].x    = 423;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][1][INDEX_VAL_SITA_NICKNAME].y    = 77;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][2][INDEX_VAL_SITA_INSCRIPTION].x  = 438;
  m_BramaGraphData.ptSitaCoords[INDEX_RAMA_AT_9_SITA][2][INDEX_VAL_SITA_INSCRIPTION].y  = 165;
//===================== Параметры 10-стульчатого стола ==============
//===================== Шаблоны доступных символов ==================
  m_BramaGraphData.PATTERN_LETTER[PAT_0][0] = b01111000;
  m_BramaGraphData.PATTERN_LETTER[PAT_0][1] = b10000100;
  m_BramaGraphData.PATTERN_LETTER[PAT_8][2] = b10100100;
  m_BramaGraphData.PATTERN_LETTER[PAT_8][3] = b01011000;
  m_BramaGraphData.LETTER_CODE[PAT_0] = '0';
  m_BramaGraphData.LETTER_CODE[PAT_1] = '1';
  m_BramaGraphData.LETTER_CODE[PAT_2] = '2';
//===================== Шаблоны надписей в кругляках ================
  m_BramaGraphData.PATTERN_INSCRIPT[PAT_INSCRIPTION_SEATOPEN][0]  = 55;
  m_BramaGraphData.PATTERN_INSCRIPT[PAT_INSCRIPTION_SEATOPEN][1]  = 56;
  m_BramaGraphData.PATTERN_INSCRIPT[PAT_INSCRIPTION_SEATOPEN][2]  = 124;
  m_BramaGraphData.PATTERN_INSCRIPT[PAT_INSCRIPTION_SEATOPEN][3]  = 215;
//===================== Текст надписей в кругляках ==================
  lstrcpy(m_BramaGraphData.INSCRIPTION_TEXT[PAT_INSCRIPTION_SEATOPEN],  "SEATOPEN");
  lstrcpy(m_BramaGraphData.INSCRIPTION_TEXT[PAT_INSCRIP
    
            

© Geektimes