- PVSM.RU - https://www.pvsm.ru -
Йо-хо-хо!
Пока IT сообщество увлеченно наблюдает за криптовалютами и их добычей, я решил помайнить то, что майнилось задолго до того, как крипта и все связанное с ней стало мэйнстримом. Речь конечно же об игровом золоте в ММО играх.
Реализовать задумку мне помог python 3.6 и советы коллег программистов. Хотя статья и будет опираться на пример в конкретной игре, цель ее больше не рассказать историю хака, а расхвалить питон и показать еще не освоившим, что с ним может делать человек-не-программист и почему это так круто.
Сразу стоит сделать некоторые ремарки:
Есть игра GuildWars2, в ней запущен рождественский эвент (мини игра), которая очень похожа на guitar hero только с колокольчиком.
Смысл игры заключается в своевременном нажатии кнопок-нот 1-2-3-4 и 6-7-8-9 в зависимости от того, какой кружок по какой дорожке приедет к центру, где стоит персонаж. Если очень интересно, то можно посмотреть видео на ютубе, набрав guild wars 2 choir bell.
За полное прохождение, пусть и с погрешностями, дают максимальное количество ценных подарочков, которые можно продать на рынок по цене примерно 5.5 серебра за штуку. Я подсчитал, что за сутки неприрывного прохождения этого эвента, можно делать ~3300 подарочков а это больше 180 чистого золота, цена которому 2 рубля за ед. на черном рынке. Копейки в абсолютных величинах но очень неплохо в сравнении с тем же криптомайнингом, а? Особенно если учесть, что для этого нам не требуется дорогостоящая видеокарта или платный аккаунт.
В общем мои рученки идейного ботовода зачесались и я решил сие действо автоматизировать, хотя бы из спортивного интереса.
Для автоматизации нам нужно всего 2 вещи: распознавать пиксели и нажимать кнопки.
Тут требуется сделать небольшое отступление и сказать, что я совсем не будучи программистом, пробовал быдлокодить на с++, с#, PHP, delphi и даже ассемблере в среде masm32. Выбор питона на этой стадии был почти случайным. Я просто подумал «А почему бы не попробовать до кучи на питоне? Вдруг будет удобнее?». Это не был какой то осознанный выбор, я тогда еще и не предполагал, насколько питон классный.
Нужно было понять, как можно зацепиться за цвета пикселей и я начал искать простую пипетку, которая показывала бы цвет под указателем мышки и его rgb значение. Из игры я записал на бандикам видео для анализа, поэтому фотошоп и редакторы изображений не подходили. Не нарезать же в самом деле видео на скриншоты. Мне нужно было что то простое, работающее в режиме реального времени и показывающее координаты. К сожалению гугл не нашел мне подходящей утили и я решил состряпать ее сам. Вот что получилось:
from graphics import *# Здесь и далее импортируемый модуль графических примитивов(скачивается отдельно)
import pyautogui #Здесь и далее импортируемый модуль автоматизации ( скачивается отдельно )
import time # время же. (Стандартный модуль)
def main():# определяем мэйн функцию
win = GraphWin("pipetka", 200, 200, autoflush=True)#создаем графическую форму размером 200х200 и элементы на ней
x, y = pyautogui.position()#получаем в x, y координаты мыши
r, g, b = pyautogui.pixel(x, y)# получаем в r, g, b цвет
ColorDot = Circle(Point(100, 100), 25)# создаем точку, отображающую цвет
ColorDot.setFill(color_rgb(r, g, b))# устанавливает ей заливку из ранее полученных цветов
ColorDot.draw(win)# рисуем на форме win
RGBtext = Entry(Point(win.getWidth()/2, 25), 10)# создаем RGB вывод
RGBtext.draw(win)# рисуем на форме win
RGBstring = Entry(Point(win.getWidth()/2, 45), 10)#создаем вывод цвета в web стиле
RGBstring.draw(win)# рисуем на форме win
Coordstring = Entry(Point(win.getWidth() / 2, 185), 10)# создаем отображение координат
Coordstring.draw(win)# рисуем на форме win
while True: # цикл перереисовки формы
time.sleep(0.1)# задержка в 0.1 с, чтобы питон не сходил с ума
x, y = pyautogui.position()#получаем в x, y координаты мыши
r, g, b = pyautogui.pixel(x, y)# получаем в r, g, b цвет
ColorDot.setFill(color_rgb(r, g, b))#Обновляем цвет
RGBtext.setText(pyautogui.pixel(x, y))#Обновляем RGB
RGBstring.setText(color_rgb(r, g, b))#Обновляем web цвет
Coordstring.setText(str(x)+" "+ str(y) )#Обновляем координаты
win.flush()# Даем команду на перерисовку формы
#основной код начинается ниже.
main()#вызываем нашу функцию.
Тулза в действии. Поковырялся я с ней и с прискорбием обнаружил, что моя первоначальная задумка захватывать пиксели уже на середине, провальная:
1) мало времени на определение цвета и посыл нажатия
2) цвета кружков очень неоднородные
3) незначительные отклонения в позиционировании камеры сильно мешают
Однако поковырявшись еще чуть чуть, мне в голову пришла идея захватывать не пиксели нужного цвета а изменения яркости в нужных местах т.к. эксперементы с пипеткой показали, что кружки гораздо выше по шкале R, G или B ( в зависимости от цвета) нежели фон игрового поля. В итоге я выбрал 8 точек где кружки проходят в наибольшем размере.
import time# модуль времени ( стандартный )
import pyautogui# автоматизатор ( скачивается отдельно )
import winsound#модуль для сигнала, чтобы знать, что программа запустилась ( стандартный )
import keyboard#модуль для работы с клавиатурой ( скачивается отдельно )
def analyzer():
etalon = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),
pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145),
pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ]#массив эталонных цветов
trigger = [0,0,0,0,0,0,0,0]#массив триггеров, инициализируем нулями
while True:
change = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),
pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145),
pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ] #Забираем в цикле change массив текущего состояния точек
for nomer in range(0,8):
if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:
if trigger[nomer] == 0: #если точка изменила цвет по R,G или B больше чем на +50 и триггер прохождения выключен
trigger[nomer] = 1#включаем триггер прохождения
else:
if trigger[nomer] == 1: #проверяем не включен ли триггер прохождения
trigger[nomer] = 0 #обнуляем триггер прохождения
print("push " +str(nomer) + time.strftime(' %X'))
#основной код начинается ниже.
keyboard.wait(combination="home")#после старта ждем нажатия клавиши "Home"
winsound.Beep(1000, 100) #сигналим что программа стартанула
analyzer()# запускаем анализ
Разбор алгоритма:
keyboard.wait(combination="home")
нужна как раз, чтобы забрать эталонные цвета в момент, когда игра уже развернута и камера отцентрирована
for nomer in range(0,8):
if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:
if trigger[nomer] == 0:
trigger[nomer] = 1
if trigger[nomer] == 1:
trigger[nomer] = 0
print("push " +str(nomer) + time.strftime(' %X'))
В игре отслеживать правильность работы и отлаживать трудно, поэтому я опять сделал запись окна игры на бандикам и тестировал на нем.
Слева — то, что выдал вывод питона, справа — то, что записал я, просматривая видео в замедленном режиме. Как видите есть погрешности в виде лишних нажатий 6 и 0 — это чертовы снежинки, которые никак нельзя убрать. Штука в том, что для исходной цели, непрерывного сбора подарочков, нам не нужен 100% счет, игра прощает игроку довольно много ошибок. Если бы это было не так, то просто нужно было бы ввести дополнительную проверку на белый цвет.
Тут собственно ничего заумного нет. Мы получаем номер точки и через 2 секунды нажимаем кнопку. Почему через 2 секунды? Потому, что кругляш с момента когда мы его засекли доезжает до середины примерно за 2 секунды. Небольшая хитрость тут заключается в том, что нажимать кнопки нужно независимо друг от друга. Мы не можем ставить исполнение программы на паузу применив классический sleep() т.к. все собьется да и вообще кругляши летят достаточно быстро. Можно организовать очередь или воспользоваться потоками, что, как мне думается, является более изящным решением( если конечно питон и много потоков не тормозят на вашем ПК ).
Добавляем в код
from threading import Timer
и сам обработчик отложенного нажатия
def delaypress(keynum):
if keynum < 4:
keynum +=1
else:
keynum +=2
t = Timer(2, keyboard.send, args=[str(keynum)])
t.start()
Если на вход поступает номер точки 0-1-2-3 нажимаем через 2 секунды номер точки + 1, если же поступает 4-5-6-7, то нажимаем через 2 секунды номер + 2 ( т.к. кнопка 5 не задействована в мини игре )
import time# модуль времени ( стандартный )
import pyautogui# автоматизатор ( скачивается отдельно )
import winsound#модуль для сигнала, чтобы значть, что программа запустилась ( стандартный )
import keyboard#модуль для работы с клавиатурой ( скачивается отдельно )
from threading import Timer#импортим таймер из модуля потоков ( стандартный )
def delaypress(keynum):
if keynum < 4:
keynum +=1
else:
keynum +=2
t = Timer(2, keyboard.send, args=[str(keynum)])
t.start()
def analyzer():
etalon = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),
pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145),
pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ]#массив эталонных цветов
trigger = [0,0,0,0,0,0,0,0]#массив триггеров, инициализируем нулями
while True:
change = [pyautogui.pixel(355, 288), pyautogui.pixel(460, 200), pyautogui.pixel(600, 130),
pyautogui.pixel(735, 112), pyautogui.pixel(875, 109), pyautogui.pixel(1000, 145),
pyautogui.pixel(1139, 203), pyautogui.pixel(1260, 290) ] #Забираем в цикле change массив текущего состояния точек
for nomer in range(0,8):
if change[nomer][0] > etalon[nomer][0]+50 or change[nomer][1] > etalon[nomer][1]+50 or change[nomer][2] > etalon[nomer][2]+50:
if trigger[nomer] == 0: #если точка изменила цвет по R,G или B больше чем на +50 и триггер прохождения выключен
trigger[nomer] = 1#включаем триггер прохождения
else:
if trigger[nomer] == 1: #проверяем не включен ли триггер прохождения
trigger[nomer] = 0 #обнуляем триггер прохождения
#print("push " +str(nomer) + time.strftime(' %X'))#заменяем печать на вызов нажималки
delaypress(nomer)
#основной код начинается ниже.
keyboard.wait(combination="home")#после старта ждем нажатия клавиши "Home"
winsound.Beep(1000, 100) #сигналим что программа стартанула
analyzer()# запускаем анализ
А теперь, как и обещано, расхваливаю питон( надеюсь достаточно обоснованно ).
Pyautogui документация [1]
Graphics документация [2]
Документация по модулю keyboard [3]
Если есть интересные идеи для хаков на питоне — пишите в личку.
Автор: Артем Артамонов
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/271963
Ссылки в тексте:
[1] Pyautogui документация: http://pyautogui.readthedocs.io/en/latest/cheatsheet.html
[2] Graphics документация: https://anh.cs.luc.edu/python/hands-on/3.1/handsonHtml/graphics.html
[3] Документация по модулю keyboard: https://pypi.python.org/pypi/keyboard/
[4] Источник: https://habrahabr.ru/post/346146/?utm_source=habrahabr&utm_medium=rss&utm_campaign=346146
Нажмите здесь для печати.