Pull to refresh

Принципы работы покерного бота

Reading time 8 min
Views 96K
image


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

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

Вообще самым первым ботом (точнее программой, играющей в покер) считается «Orac», который был разработан в начале 80-х известным покерным игроком Майком Каро, автором книги «Язык жестов». Одной из особенностей программы была возможность пользоваться тайминг-телзами — если оппонент долго думал, то его действия с большей вероятностью считались блефом, чем если он действовал быстро.

В Интернете не так сложно найти много готовых ботов. От бесплатных простейших экземпляров, до ботов с большим набором функций и возможностью командной игры на нескольких аккаунтах ценой $200. И это только в общедоступных источниках, неизвестно что можно найти на каких-нибудь специализированных закрытых хакерских форумах и сайтах. По слухам хороший выигрывающий бот стоит от $1000, есть экземпляры и по $5000. Такие программы наверняка очень хорошо умеют скрывать себя и максимально имитируют поведение человека, вполне возможно используют нейросети для принятия решений. В этой статье мы не будем писать бота за $5к, мы просто постараемся разобрать основные принципы работы программ попроще. А дальнейшему совершенствованию нет предела.

Основы работы бота


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

В этой статье мы поговорим о вводе/выводе информации — основных программных модулях покерного бота.

Получение информации


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

В лог-файл обычно не попадает вся информация по раздаче, например, из лог-файла клиента Pokerstars (C:\Program Files\PokerStars\PokerStars.log.0) можно узнать только карты которые нам раздали и позицию диллера:
MSG_TABLE_SUBSCR_ACTION
MSG_TABLE_SUBSCR_DEALPLAYERCARDS
sit0
nCards=2
sit1
nCards=2
sit2
nCards=2
sit3
nCards=2
sit4
nCards=2
sit5
nCards=2
dealerPos=3
TableAnimation::dealPlayerCards
MSG_TABLE_PLAYERCARDS 000C0878
::: 11c
::: 11d

11с, 11d — наши карты (JcJd), а диллер на 3-м месте.

Способ с API-сообщениями достаточно прост в реализации и часто с его помощью можно получить всю необходимую информацию. Для его реализации нужно использовать внедрение DLL в процесс покерного клиента. Внедренная DLL-ка может нам пригодиться для имитации нажатия клавиш и другого вывода информации. Основной минус инжекта в том, что сложно скрыть такое воздействие на клиент, если он пытается отлавливать такие попытки. Но программа не может воспринимать все внедрения как взлом, потому что эти методы используют вполне честные программы, например всем известный «Punto Switcher».

Для внедрения DLL существует несколько способов:
1. Внедрение через реестр.
2. Использование ловушек (хуков).
3. Внедрение с помощью удаленного потока
4. Запись напрямую в память с помощью WriteProcessMemory(), подробнее можно почитать здесь.

Мы рассмотрим самый простой и удобный подход — использование ловушек. Для этого нужно использовать API-функцию SetWindowsHookEx(idHook, lpfn, hMod, dwThreadId), где

idHook — определяет тип процедуры захвата, для глобального перехвата необходимо использовать WH_CBT (для перехвата сообщений клавиатуры, например, можно использовать WH_KEYBOARD);

lpfn — указатель на процедуру перехвата, которая будет вызываться каждый раз при перехвате. В ней мы будем отлавливать нужные нам сообщения и выполнять необходимые действия;

hMod — дескриптор DLL-ки в которой содержится процедура lpfn.

dwThreadId — идентификатор потока на который устанавливается перехватчик (0 для глобального перехвата).

В нашей DLL обязательно должна быть функция установки ловушки и функция вызываемая при срабатывании этой ловушки:
BOOL WINAPI SetHook() {
  g_hook = SetWindowsHookEx(WH_CBT, (HOOKPROC) CBTProc, g_hinstDll, 0);
  return (g_hook != NULL);
}

LRESULT WINAPI CBTProc(int nCode, WPARAM wParam, LPARAM lParam) {

  if (nCode < 0)
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
 
  if (nCode == HCBT_ACTIVATE)
  {
    //Что-нибудь сделать при активации окна
    //..
  }
  else if (nCode == HCBT_DESTROYWND)
  {
    //Что-нибудь сделать при закрытии окна
    //..
  }
  else if (nCode == HCBT_SETFOCUS)
  {
    //Что-нибудь сделать при получении фокуса
    //..
  }
 
  //Передаем управление следующим ловушкам в цепочке
  return(CallNextHookEx(g_hook, nCode, wParam, lParam));
}


* This source code was highlighted with Source Code Highlighter.

При установке глобального перехвата, DLL встраивается в каждый процесс в системе, чтобы не занимать из-за этого много памяти можно разбить процесс загрузки на две части. Сначала внедряется глобальная DLL, которая занимает минимум памяти и умеет только определять в каком процессе она загружена. Для нужного процесса она с помощью LoadLibrary() подгружает вторую DLL в которой и реализован необходимый нам функционал (чтение карт, логика и т.д.).

После перехвата мы можем отлавливать разные API-сообщения, которые посылаются клиенту. Например, при выводе в элемент Rich Edit (может использоваться для организации чата) используется сообщение EM_STREAMIN. И мы можем перехватить его для получения выводимого в чат текста, а вместе с ним и информации по раздаче. Для каждого рума элемент для вывода текста может быть индивидуален, но порядок действий такой же. Вообще для исследования передаваемых клиенту API-сообщений очень полезно использовать программу Spy++ (большинство из вас с ней знакомо, она входит в пакет Visual Studio) или аналог. С помощью Spy++ можно узнать заголовки нужных нам окон и узнать какие API-сообщения нам нужно перехватывать.

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

Если невозможно устроить перехват (клиент блокирует такие попытки) или никак не удается отыскать необходимую нам информацию, мы можем использовать метод захвата экрана и распознавания по нему символов. Но этот способ лучше оставлять на крайний случай, потому что он более трудоемкий и требует больше ресурсов при работе. Главное его преимущество, что этот способ не сможет засечь покерный клиент. Можно вообще запускать покерного бота на другом компьютере (хотя часть отвечающая за нажатие клавиш должно быть на компьютере с клиентом, но для этой части не обязательно использовать внедрение DLL), куда передается видео с экрана компьютера с покерным клиентом. Еще можно запускать клиент под виртуальной машиной, а бота под основной ОС. Очевидные минусы подхода с захватом экрана — количество играемых столов ограничено разрешением экрана и зависимость от темы карт и стола используемых в клиенте.

Симуляция действий пользователя


При нажатии ботом кнопок и других действиях имитирующих поведение обычного игрока нам нужно достичь максимальной правдоподобности. При этом нужно использовать случайную задержку ответа бота, чтобы не делать все действия моментально после начала хода. Из статьи о ГСЧ можно узнать, что покер рум PokerStars использует движения мышью пользователя для генерации случайных чисел. При этом ничего не мешает им использовать эту информацию и для проверки пользователей (вполне вероятно что и другие покерные клиенты ведут такую «слежку» за своими игроками). Поэтому важно совершать случайные движения мыши по экрану и передвижение курсора в точку нажатия кнопок. Еще можно делать случайные клики за пределами окна покерного клиента (по рабочему столу, панели задач).

Поэтому оптимально будет работать с мышью программно напрямую. Есть вариант находить хэндл нужных кнопок и посылать им сообщение с помощью SendMessage(), но лучше минимально воздействовать на сам клиент, а делать все извне. Получается нужно найти локальные координаты кнопок в окне, для этого можно использовать все тот же Spy++. Если настроить его для отлова сообщений мыши в покерном клиенте, то при нажатии на нужную область мы получим и локальные координаты клика в окне. Примерно так:
<00227> 00120644 P WM_LBUTTONDOWN fWKeys:MK_BUTTON xPos:840 yPos:103
Так можно найти координаты прямоугольника внутри кнопки, из которого случайно нужно выбирать точку для нажатия, чтобы имитировать поведение человека.

Для управления мышью будем использовать API-функцию SendInput(UINT nInputs, LPINPUT pInputs, int cbSize). Ей передается массив структур INPUT, который содержит последовательные действия с мышью и клавиатурой. Так выглядит код перемещения мыши в определенную позицию и нажатие ее левой кнопки:
//Координаты в окне клиента
POINT coords;
coords.x = 840;
coords.y = 103;

//Конвертируем в координаты экрана
ClientToScreen(hWND, &coords);

//Получаем разрешение экрана
HDC hdc = GetDC(NULL);
int screenWidth = GetDeviceCaps(hdc, HORZRES);
int screenHeight= GetDeviceCaps(hdc, VERTRES);
ReleaseDC(NULL, hdc);

//Конвертируем координаты в глобальные
double worldCoords = 65535 * coords.x;
double buttonX = worldCoords / screenWidth;
worldCoords = 65535 * coords.y;
double buttonY = worldCoords / screenHeight;

// Создаем массив структур INPUT
INPUT input[3];
MOUSEINPUT mouseInput;

// Двигаем мышь к кнопке
input[0].type=INPUT_MOUSE;
mouseInput.dx = (int)buttonX;
mouseInput.dy = (int)buttonY;
mouseInput.mouseData = NULL;
mouseInput.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
mouseInput.time = 0; //Здесь можно использовать случайное время 1-2с.
mouseInput.dwExtraInfo = 1001;
input[0].mi = mouseInput;

// Нажимаем левую кнопку мыши
input[1].type=INPUT_MOUSE;
mouseInput.dx = (int)buttonX;
mouseInput.dy = (int)buttonY;
mouseInput.mouseData = NULL;
mouseInput.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
mouseInput.time = 0; //Здесь можно использовать случайное время 1-2с.
mouseInput.dwExtraInfo = 1001;
input[1].mi = mouseInput;

// И отжимаем...
input[2].type=INPUT_MOUSE;
mouseInput.dx = (int)buttonX;
mouseInput.dy = (int)buttonY;
mouseInput.mouseData = NULL;
mouseInput.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE;
mouseInput.time = 0; //Здесь можно использовать случайное время 1-2с.
mouseInput.dwExtraInfo = 1001;
input[2].mi = mouseInput;

int numberOfInputs = 2;

// Посылаем наш INPUT
SendInput(numberOfInputs, input, sizeof(INPUT));


* This source code was highlighted with Source Code Highlighter.

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

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

Статья Pokeroff.ru специально для Хабрахабра
Tags:
Hubs:
+61
Comments 82
Comments Comments 82

Articles

Information

Website
www.pokeroff.ru
Registered
Founded
2006
Employees
2–10 employees
Location
Латвия