Я уже видел ботов, которые умеют ловить рыбу, работающие в свернутом режиме не перехватывая управления над компьютером. Также я знаю насколько беспощадны близард по вопросам банов читеров. Изменение данных в оперативной памяти легко определяется встроенным античитом. Ну и последнее — на мак я не нашёл ни одного бота.
Поэтому я решил закрыть все эти вопросы разом и сделать бота, который будет перехватывать управление мыши, кидать поплавок, и тыкать на него на экране когда нужно. Как я полагал python располагает широким выбором инструментов для автоматизации таких штук, и не ошибся.
Немножечко погуглив, я нашёл OpenCV, в котором есть поиск шаблону с не сложным гайдом. С помощью него мы и будем искать наш поплавок на экране.
Сперва мы должны получить саму картинку с поплавком. Ищем и находим библиотеку pyscreenshot с гайдом как делать скриншоты, немножечко редактируем:
import pyscreenshot as ImageGrab
screen_size = None
screen_start_point = None
screen_end_point = None
# Сперва мы проверяем размер экрана и берём начальную и конечную точку для будущих скриншотов
def check_screen_size():
print "Checking screen size"
img = ImageGrab.grab()
# img.save('temp.png')
global screen_size
global screen_start_point
global screen_end_point
# я так и не смог найти упоминания о коэффициенте в методе grab с параметром bbox, но на моем макбуке коэффициент составляет 2. то есть при создании скриншота с координатами x1=100, y1=100, x2=200, y2=200), размер картинки будет 200х200 (sic!), поэтому делим на 2
coefficient = 2
screen_size = (img.size[0] / coefficient, img.size[1] / coefficient)
# берем примерно девятую часть экрана примерно посередине.
screen_start_point = (screen_size[0] * 0.35, screen_size[1] * 0.35)
screen_end_point = (screen_size[0] * 0.65, screen_size[1] * 0.65)
print ("Screen size is " + str(screen_size))
def make_screenshot():
print 'Capturing screen'
screenshot = ImageGrab.grab(bbox=(screen_start_point[0], screen_start_point[1], screen_end_point[0], screen_end_point[1]))
# сохраняем скриншот, чтобы потом скормить его в OpenCV
screenshot_name = 'var/fishing_session_' + str(int(time.time())) + '.png'
screenshot.save(screenshot_name)
return screenshot_name
def main():
check_screensize()
make_screenshot()
Получаем примерно следующую картинку:
Далее — найти поплавок. Для этого у нас должен быть сам шаблон поплавка, который мы ищем. После сотни попыток я всё таки подобрал те, которые OpenCV определяет лучше всего. Вот они:
Берём код из ссылки выше, добавляем цикл и наши шаблоны:
import cv2
import numpy as np
from matplotlib import pyplot as plt
def find_float(screenshot_name):
print 'Looking for a float'
for x in range(0, 7):
# загружаем шаблон
template = cv2.imread('var/fishing_float_' + str(x) + '.png', 0)
# загружаем скриншот и изменяем его на чернобелый
src_rgb = cv2.imread(screenshot_name)
src_gray = cv2.cvtColor(src_rgb, cv2.COLOR_BGR2GRAY)
# берем ширину и высоту шаблона
w, h = template.shape[::-1]
# магия OpenCV, которая и находит наш темплейт на картинке
res = cv2.matchTemplate(src_gray, template, cv2.TM_CCOEFF_NORMED)
# понижаем порог соответствия нашего шаблона с 0.8 до 0.6, ибо поплавок шатается и освещение в локациях иногда изменяет его цвета, но не советую ставить ниже, а то и рыба будет похожа на поплавок
threshold = 0.6
# numpy фильтрует наши результаты по порогу
loc = np.where( res >= threshold)
# выводим результаты на картинку
for pt in zip(*loc[::-1]):
cv2.rectangle(src_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
# и если результаты всё же есть, то возвращаем координаты и сохраняем картинку
if loc[0].any():
print 'Found float at ' + str(x)
cv2.imwrite('var/fishing_session_' + str(int(time.time())) + '_success.png', src_rgb)
return (loc[1][0] + w / 2) / 2, (loc[0][0] + h / 2) / 2 # опять мы ведь помним, что макбук играется с разрешениями? поэтому снова приходится делить на 2
def main():
check_screensize()
img_name = make_screenshot()
find_float(img_name)
Итак, у нас есть координаты поплавка, двигать курсор мыши умеет autopy буквально с помощью одной строки, подставляем свои координаты:
import autopy
def move_mouse(place):
x,y = place[0], place[1]
print("Moving cursor to " + str(place))
autopy.mouse.smooth_move(int(screen_start_point[0]) + x , int(screen_start_point[1]) + y)
def main():
check_screensize()
img_name = make_screenshot()
cords = find_float(img_name)
move_mouse(cords)
Курсор на поплавке и теперь самое интересное — как же узнать когда нужно нажать? Ведь в самой игре поплавок подпрыгивает и издает звук будто что-то плюхается в воду. После тестов с поиском картинки я заметил, что OpenCV приходится подумать пол секунды прежде чем он возвращает результат, а поплавок прыгает даже быстрее и изменение картинки мы врядли сможем определить с помощью OpenCV, значит будем слушать звук. Для этой задачки мне пришлось поковырять разные решения и остановился вот над этим — пример использования гугл апи для распознавания голоса, оттуда мы возьмём код, который считывает звук.
import pyaudio
import wave
import audioop
from collections import deque
import time
import math
def listen():
print 'Listening for loud sounds...'
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 18000 # битрейт звука, который мы хотим слушать
THRESHOLD = 1200 # порог интенсивности звука, если интенсивность ниже, значит звук по нашим меркам слишком тихий
SILENCE_LIMIT = 1 # длительность тишины, если мы не слышим ничего это время, то начинаем слушать заново
# открываем стрим
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
# output=True, # на мак ос нет возможности слушать output, поэтому мне пришлось прибегнуть к использованию <a href="https://github.com/RogueAmoeba/Soundflower-Original">Soundflower</a>, который умеет перенаправлять канал output в input, таким образом мы перехватываем звук игры будто это микрофон
input=True,
frames_per_buffer=CHUNK)
cur_data = ''
rel = RATE/CHUNK
slid_win = deque(maxlen=SILENCE_LIMIT * rel)
# начинаем слушать и по истечении 20 секунд (столько максимум длится каждый заброс поплавка), отменяем нашу слушалку.
success = False
listening_start_time = time.time()
while True:
try:
cur_data = stream.read(CHUNK)
slid_win.append(math.sqrt(abs(audioop.avg(cur_data, 4))))
if(sum([x > THRESHOLD for x in slid_win]) > 0):
print 'I heart something!'
success = True
break
if time.time() - listening_start_time > 20:
print 'I don\'t hear anything during 20 seconds!'
break
except IOError:
break
# обязательно закрываем стрим
stream.close()
p.terminate()
return success
def main():
check_screensize()
img_name = make_screenshot()
cords = find_float(img_name)
move_mouse(cords)
listen()
Последнее, что осталось — споймать саму рыбку, когда мы услышим звук, снова используем autopy:
def snatch():
print('Snatching!')
autopy.mouse.click(autopy.mouse.RIGHT_BUTTON)
def main():
check_screensize()
img_name = make_screenshot()
cords = find_float(img_name)
move_mouse(cords)
if listen():
snatch()
По моим тестам, что я оставлял бота рыбачить по ночам, за неделю такого абуза он сделал около 7000 бросков и
Полный код можно посмотреть тут, но очень советую не использовать для абуза фишинга, ибо автоматизация процессов игры нарушает пользовательское соглашение и можно получить бан.
Буду рад любым комментариям.
Комментарии (78)
kovalevsky
14.08.2017 13:19+3Хорошая идея, но Вам прилетит бан в следующем «бан-вейве». Вы действительно думаете, что до того, как лезть в память, никто не искал поплавок на экране? :)
Я уверен Вы попались сразу же по движению курсора, которое не характерно реальному человеку, а идёт ровно от Х1, Y1 до X2, Y2.kio_tk Автор
14.08.2017 13:26Конечно искали, но статьи такой я не нашел и подумал было бы интересно написать — мало ли в каких других ситуациях или играх понадобится такая штука.
Можно про «бан-вейв» подробнее? Уже прошло пару месяцев как я последний раз его запускал и пока ничего страшного не произошло.foldr
14.08.2017 13:40+2Да множество случаев. Устраивают не часто, может и полгода полет будет успешный, как повезет
kio_tk Автор
14.08.2017 13:50+1Тогда это будет наконец достойный повод завязать. Но пока бана нет — это того стоило
kovalevsky
14.08.2017 14:26+1Сделайте второй аккаунт, создайте гильдию и прячьте деньги в гильд-банк. Аккаунт с ботом, скорее всего, забанят, а вот деньги из ГБ не забирают :)
kio_tk Автор
14.08.2017 14:50Если речь о деньгах, то заработал всего тысячу-полтора за эту неделю ночных абузов, так что не очень выгодно, но ведь маунта, ачивки и игрушки не скинешь… (Только не смейтесь)
Desprit
14.08.2017 14:50Добавить немного шума в итоговые координаты + сделать шанс на «случайный» промах по поплавку. Чем это будет отличаться от реального человека? Разумеется, за 20 часов мимо может много раз кто-то пробежать, заговорить, не получиться ответ и кинуть репорт, но это уже вообще другой разговор)
dimkss
14.08.2017 17:31Еще можно двигать курсор с ускорение. И добавить редкие рандомные движения курсора по экрану в случайные отрезки времени. И время от времени открывать инвентори, или статы какие-то.
kio_tk Автор
14.08.2017 18:47+3И переписку с другим ботом, которого нужно будет сделать специально чтобы поддерживать «жизнь» нашего рыболова. Благо достаточно всяких апи с говорящими ботами.
bigbrotherwatchingyou
15.08.2017 08:49+2заказать внутриигровую майку:
«1. Только пришел
2. Не клюёт
3. На червя
4. Водку буду»
bro-dev
14.08.2017 13:44Недавно была статья про openai играет в доту, тоже на питоне. Но там задача значительно сложнее, как они решают эту проблему определения где что на экране? мб есть идеи?
kio_tk Автор
14.08.2017 14:02Я знаю, что Source 2 довольно не новый движок, и его уже перековыряли со всех сторон. Есть боты же и для CS:GO и для той же доты, которые взрывают бомбы минеров по нужному количеству хп у противников в любой части карты.
Так что вполне возможно и внедрение. То, что было на The international, я думаю это обычное подключение бота через апи самого Source 2 — есть же обычные боты и в доте и в CS:GO.
Кроме того — я же не претендую на статью об искусственном интеллекте. Возможно их бот работает на очень мощном железе и его писали ребята, которые делают очень большие проекты на openai. Тогда они вполне могли бы и картинку обрабатывать на уровне повыше обычного метчинга темплейтов. Как раз совсем недавно общался на эту тему со знакомым, который предлагал мне сделать на python искусственный интеллект, который смог бы играть в Gravity Defied используя только картинку, но я пока не решаюсь переписывать Gravity Defied на python.
Myosotis
14.08.2017 14:12Чтобы уменьшилась вероятность бана, я бы добавила рандомные перерывы в рыбалке. Не может обычный человек играть без перерыва на пару минут всю ночь.
Demogor
14.08.2017 14:31Тогда уже и рандомные перемещения курсора и паузы между кликами.
Ну и по поводу «всей ночи» — Вы сильно недооцениваете школьников или корейских игроков)Goodkat
14.08.2017 23:47Уже не школьники были и не корейцы, но так с братом и играли, с вечера я (мне утром нужно было на работу вставать), часа в два ночи будил брата, он потом играл до утра. Не в WoW, правда.
arheops
15.08.2017 03:31+1Да ну? Не может обычный человек в РЕАЛЕ ловить рыбу по 10 часов каждую ночь, в течении последних 10 лет, включая снег и дождь(у нас так пенсионеры делают).
Demogor
14.08.2017 14:29В свое время делал очень странного бота для Rift.
У рыбалки там несколько стадий. На тот момент был готовый бот, который использовал лог-файл игры для определения этих стадий, но, насколько помню, самая важная стадия(поклевка) в логе не отображалась и бот использовал какое-то решение, которое по какой-то причине меня не устроило(а может, просто захотелось написать свой собственный, не помню).
В итоге получилось забавно — я в силу своих скромных способностей в реверсе приложений умудрился найти только функцию, отвечающую за воспроизведение звуковых файлов. В итоге перехватывал эту функцию и, в случае воспроизведения соотв. поклевке файла, производил необходимые действия.
В итоге получилось что-то средней монструозности, но, на удивление, более-менее эффективное: за относительно короткий срок вкачал рыбалку на максималку и еще какое-то время неплохо зарабатывал на рыбе.
Но вообще лучший бот, который я когда-либо использовал — https://www.solarstrike.net/ для Runes of Magic. Логика на xml+lua шикарна, бот предоставляет базовый набор функций+куча функционала и готовых waypoint'ов от других пользователей + никто не мешает писать свою логику. Чего там только народ не ваял)
Abyrvalgov
14.08.2017 14:32+1Воспользуйтесь опытом поколений! Добавьте случайные (именно случайные, а не луповые) вариации в движении курсора, в скорости реакции, во всех возможных действиях. А то ведь отловят.
LoadRunner
14.08.2017 15:15-2Мне тут товарищ подкинул идею попроще: труд детей. Своих, разумеется.
В армии с детьми проще (для командиров).
igormich88
14.08.2017 15:29Статья интересная, но думаю лучше добавить предупреждение в начале (или в конце) что эти действия нарушают пользовательское соглашение.
PS интересно есть ли смысл делать полностью внешнего бота (с веб-камерой и аппаратным эмулятором мыши) для того что бы затруднить его обнаружение?kio_tk Автор
14.08.2017 15:46+1Done.
У нас есть соседняя компания, которая занимается электроникой. Они думают, что мы занимаемся фигнёй, программируя всякие штуки, ведь все это можно сделать с помощью микросхем, механики и физики. Мы думаем то же самое о них, ведь зачем брать в руки паяльник, если клавиатуры достаточно. Так что может и есть смысл, если так проще для конкретного разработчика
На самом деле у меня не было задачи полностью скрыть использование бота, но избежать автоматического бана античита, у которого неопровержимые доказательства изменения памяти игры. Конечно запись координат курсора — тоже неопровержимое доказательство, но мне кажется это уже дикость следить за таким.igormich88
14.08.2017 18:49Я просто думал на тему более хитрого бота — что бы ротацию крутить (последовательность способностей), распознавая иконки статуса персонажа, но опасения бана и лень оказались сильнее.
DistortNeo
15.08.2017 04:06В WoW настолько суровый античит? Я просто играл в Lineage 2 на офсервере — там использовался GameGuard, но исключительно как пассивное средство, мешающее работать ботам (на самом деле нет), но не стучащее о подозрительном софте и изменении памяти процесса на сервер.
Ну а что касается от отслеживании координат и их рандомизации — мне кажется, это бесполезная затея. Игровые клиенты — толстые клиенты, они отправляют на сервер действия, а не движения мышки и клики. Возможно, античит логирует локально все движения мышки, но крайне маловероятно, что делает на их основе вердикт.
Чисто теоретически, бота от человека можно отличить по паттернам действий, но простыми наборами правил здесь не отделаться из-за огромных объёмов данных и высокой сложности. Тут уже начинают работать методы BigData. Но вот использует ли Blizzard AI для обнаружения ботов — большой вопрос.kio_tk Автор
15.08.2017 08:49В пользовательском соглашении есть пункт об наказании за автоматизации, а также пункт о разрешении сканирование компьютера и памяти на наличие всяких читерских штук. Насколько я знаю, делает это даже не сам клиент игры, а клиент батлнет, ибо он как раз и предлагает это соглашение, когда ставится. Хотя пруфов его деятельности у меня нет.
Anarions
14.08.2017 17:35Рыбачил с год, (ботом) каждую ночь, по 5-10 часов, без всяких предосторожностей (ибо на акк было пофиг), прошло три года — бан не прилетел.
OlegPyatakov
14.08.2017 18:14+1Делал ради интереса подобного бота на Sikuli (программа для автоматизации, с питоновским синтаксисом скриптов). На самом деле, все было очень похоже как в вашем примере, но с парой отличий:
- Определение момента для подсекания реализовал через отслеживание изменения области кадра с найденным поплавком.
- Строк кода вышло <100. Экономия, в основном, за счет уже готовых инструментов для поиска нужной части кадра и отказа от слежения за аудио.
kio_tk Автор
14.08.2017 18:54+1Но ведь поплавок и так качается — вы смотрели по сильному смещению поплавка? Значит у вас очень хорошо работала обработка кадра. Как я уже писал, на моем Intel Iris анализ каждого кадра в OpenCV с включенной игрой занимает 300-500мс (включая сам скриншот через ImageGrab), я посчитал это несколько неразумным в моем случае, поэтому взялся за звук.
OlegPyatakov
14.08.2017 19:29+1Да, отслеживал именно по длительному (>0,3 секунды) выходу поплавка из узкой зоны, где он обычно находится.
Сейчас проверил быстродействие визуального поиска. На ноуте с Core i7 и мобильным Radeon HD 8530M на поиск элемента по всему экрану 1366x768 уходит около 0,03 секунды. Если сократить область поиска в 5-6 раз, т.е. до примерной области рыбалки в WoW, то время на поиск уменьшается вдвое.
lxsmkv
14.08.2017 22:11все логично, ботаем вовку и диаблу, а в освободившееся время лабаем ИскИн'а на StarCraft II API
b1ackbird
14.08.2017 23:22-1Пробовал запустить скрипт Ваш, куча проблем с библиотеками питоновскими на Маке. Есть какой-то может магический девелопмент пакет для питона, о котором я не знаю? :)
mrgloom
15.08.2017 00:53Году эдак в 2007 с одногрупниками писали бота для ловли рыбы в Lineage и по-моему это был как раз UOpilot.
vsb
15.08.2017 09:05+2Делал бота, тоже для мака, правда на ObjC. Делал по-другому: после забрасывания водил мышкой по экрану и отслеживал изображение курсора. Как только курсор менялся: опять водил по экрану очерчивая границы курсора. Курсор на самом деле это ромб, как только чётко определял его координаты, ставил курсор на 1 пиксель выше верхней точки этого ромба и ждал. Когда курсор прыгает, этот ромб немного сдвигается вверх и курсор на небольшой момент меняется. В этот момент нажимал на поплавок. Работало на 100%. Самое сложное было быстро найти поплавок: если двигать мышку слишком быстро, то курсор мог не успеть измениться и можно было её «пролететь», если двигать слишком медленно, то не успевал найти прежде, чем начинает клевать. Распознавание изображений пытался сделать, но не осилил, к сожалению.
Кстати ваш способ будет плохо работать, когда в одном месте рыбачат несколько человек. В идеале надо комбинировать ваш подход для определения вероятных точек нахождения поплавка и потом проверять курсором, где он находится на самом деле.
White_Scorpion
15.08.2017 09:48Если питон позволяет (не знаю ибо не знаком) картинку поплавка можно перевести в grayscale с последующим уменьшением разрядности цвета после чего будет проще отыскивать границы поплавка.
LoadRunner
После прочтения вспоминается старый-добрый UOpilot. Интересно, он есть на Маке?
kio_tk Автор
Он умел распознавать картинку?
LoadRunner
Цвет пикселя под курсором. Или в заданных координатах тоже — уже не помню.
kio_tk Автор
Ну вот как-то сложновато выходит, ведь освещение меняется и поплавок качается, и свой алгоритм по вычислению поплавка — немножко другая задача.
Я думал что проще вычислять изменения курсора, ибо когда наводишь на поплавок, он меняется иконку на крючок, но не нашел апи курсора и не курсор сам в скриншот не попадает.
LoadRunner
Ну для работы с цветом там довольно хорошие функции:
Goodkat
Можно было вебкамерой курсор снимать, а мышку механически двигать — только хардкор, заодно и от античитов должно помочь.
Ещё периодически «ходить покурить», сворачивать игру, переключаться на фэйсбучег и т.п — когда-то давно, уже не помню, для какой игры, я так записывал макросы родной майкрософтской программулиной.
kio_tk Автор
Для этого нужно будет ставить зеркало перед маком, чтобы он смог видеть своей встроенной вебкамерой что происходит на экране. Да и в электронике я слаб, так что механическое изменение положения мышки для меня сложнее, хотя идея механически использовать трекпад несколько проще, вам не кажется?
Rampages
Все же проще купить USB Web-камеру с драйверами под мак, чем ставить зеркало. Насчет трекпада не уверен, с шариковой мышкой может быть тогда еще проще. Можно кстати взять ездящую платформу как у многих 3D принтеров, установить вместо сопла сенсор мыши (лазерный/оптический) и двигать платформу относительно сенсора.
Moon_darker
Хардкор хардкором, но проще взять какой-нибудь микроконтроллер с поддержкой USB и симулировать обычную HID мышку
Rampages
?\_(?)_/? Ну мы не ищем легких путей и любим создавать себе проблемы с заделом на будущее
edge790
Сразу вспомнил была статья про то, как сделали бота для аукциона и почты в WoW используя AutoIT и внутриигровой аддон: https://habrahabr.ru/post/113258/
equand
AutoIT умеет распозновать разницу. Я использовал его в Линейке, находил статическую позицию и использовал как торгового бота с автоматизацией ответа. Вещи за ночь продавал :)