Что нового

[Drakensang-Online] Бот для игры

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Dellroc я уже почти не кодю на автоите но суть достаточно проста:
1) Если у вас есть клиент игры, по сути у вас есть её исходник
2) Если в былые времена трафик не шифровался или шифровался статичными ключами (вшитыми в клиент), то сейчас в 99% случаев при попытке отреверсить пакеты получаемые от сервера, вы наткнетесь на динамичный шифр. То есть каждую новую сессию (а то и каждый новый пакет :D), получаемые вами данные при одном и том же содержании будут выглядеть в совершенно по другому.
3) Если учесть условие (2), то вы будете получать ключи в момент инициализации сессии (если ключи статичны в пределах сессии), или же вы будете в каждом получаемом пакете иметь так же связку ключей (если ключи динамичны в пределах сессии).
4) Вы НЕ СМОЖЕТЕ отреверсить ключи работая только с памятью, только с пакетами или только с ассемблерными листингами, вам надо работать со всем одновременно.
5) Схема достаточно проста, вам нужен отладчик, например OllyDBG так же стоит укомплектоваться снифферами и взломщиками памяти, из взломщиков памяти лучше чем CheatEngine человечество еще не придумало, он помимо тупого поиска значения в памяти (что по сути является далеко не главным его преимуществом, иначе подошел бы и примитивный Cheat Omatic), так же может выводить вам исходный ассемблерный код программы, и при условии что вы в состоянии понимать что там написано, вы получаете абсолютный контроль над клиентом, так же с его помощью вы можете модифицировать код программы с которой работаете, в пределах сессии конечно же. Про снифферы нет смысла разглагольствовать, их сотни, в различных вариантах, тот же WPE Pro.
6) Нужна точка привязки. Тут всё зависит от конкретной игры и того как тамошние кодеры написали процесс инициализации. Либо клиент получает ключи от сервера в момент запуска (наиболее маловероятный вариант, т.к. при таком раскладе, собственное детище фирмы могло бы спокойно за ддосить создателя), либо вы получаете ключи при успешной авторизации (наиболее вероятно при условии статичных ключей в пределах сессии). Например: В клиент вшит какой-нибудь примитивный алгоритм шифрования, который используется лишь в момент инициализации, дабы отправить авторизационные данные серверу, в хоть сколько нибудь не читаемом на глаз виде, подойдет даже ксоринг. Клиент отправляет логин и пароль пользователя в плохозащищенном формате и в ответ в случае успеха получает ключи и разрешение на работу, если же логин и пароль были неправильными, сервер даже не почешется вернуть клиенту какую-то сколько-нибудь стоящую инфу, он даже не почешется инициализировать сколько-нибудь сложные алгоритмы геренации ключей или еще что, он вернет что-то столь же примитивно зашифрованное как и авторизационный пакет, говорящее о том что "шел бы ты подальше". И прошу заметить что это ключевой момент. Вы получили ответ от сервера, каким бы он небыл - это результат. Вы имеете что-то например похожее на: "5c0xF ff5839CC ... fafa32 N". И это самое важное событие в вашей жизни. Это и есть тот пакет который вы обязаны отреверсить если хотите пройти дальше. Тут видимо пора начать следующий пункт.
7) Кнопка Login. Да, именно она инициализирует связь с сервером в 99% случаев. И пользуясь отладчиком типа OllyDBG, вы сможете по крайней мере пошагово отследить действия, которые проводит клиент с логином и паролем прежде чем отправить их на сервер. Да, для вас это вероятнее всего будет бессмысленная не имеющая особой логики цикличная операция. НО вы должны понимать принципы работы шифрования. в том же RSA происходит побайтовое кодирование сообщения с преобразованием его в набор не несущих смысла цифр, В ЦИКЛЕ. Да тот же ксоринг может так сделать. И если вы наткнетесь на цикличный кусок кода, который принимает например 1 символ, а выплевывает набор цифр или же набор хексов или еще что, вы попали в яблочко грубо говоря. Имея ассмеблерный код этой функции вы сможете восстановить её исходник, который позволит вам хотя бы формировать пакеты авторизации. Далее следующий шаг.
8) Реверс пакета несущего в себе инфу о положительной авторизации от сервера. В 99% случаев функцией что принимает результат будет совершенно иная функция нежели та что его отправляла, ввиду того что полученный на успешную автоизацию пакет будет нести в себе сложную информацию закодированный динамичными ключами. Тут вы опять же имея приложенный к процессу отладчик, сможете отследить процесс получения информации со всеми его ключами, вы сможете увидеть где в полученном сообщении располагаются ключи и как в дальнейшем они применяются к сообщению в целом с целью получения raw_message. Которое и является тем самым сообщением, говорящем клиенту о том что теперь он может работать.
9) В большинстве случаев raw_message и окажется носителем ключей.
CAUTION
В подавляющем большинстве случаев, все ключи имеют срок жизни. Например я, программируя свои сервера принятия решений, выставляю срок жизни не более нескольких секунд. После этого времени, даже если полученная инфа является валидной, сервер отклоняет клиент ввиду того что срок жизни ключей прошел. Уже в попытках отреверсить пакеты, вы можете наткнуться на бан т.к. время затраченное вами на реверс пакета, может привышать срок его жизни. Это беда, да, но деваться некуда, вы должны расчитывать примерно на 15-20 банов аккаунтов с момента когда вы решили начать реверсить пакет и до момента его успешного реверса.

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

P.S. Под восстановлением исходного кода я имею ввиду не то что думают большинство пользователей автоита "перетащил ехешничек в окошко и получил исходник", а полноценный реверс, когда вы читая ассемблерный листинг восстанавливаете логику его поведения с нуля, фактически пишите функцию заново.


Добавлено:
Сообщение автоматически объединено:

P.S.S. Да, я думал о написании подобного гайда, но для Diablo II. Хотя еще даже не приступал, времени нету


Добавлено:
Сообщение автоматически объединено:

P.S.S.S. Чем больше я пытаюсь монетизировать свои знания, тем больше я понимаю что подобные посты деструктивны для меня самого
 

lirikmel

Продвинутый
Сообщения
226
Репутация
84
OffTopic:
афигеть я еле прочитал


И все же вам не кажется что вы полезли в "дебри", пакетный уровень....ASM , вы точно ошиблись форумом . Все таки Autoit простой язык и задачи он решает простые , если хотите делать на Autoit то и делаете проще.

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


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

Передвижение персонажа это уже отдельная тема , хотите "умного бота" волновой и тп алгоритм вам в руки НО тогда сразу autoit уже вам не подойдет , пишите на чем нибудь другом. Передвижение по поинтам вполне, изометриявсе таки в игре , координатную сетку знаем, разбиваем вокруг персонажа координатные оси делим по 25-45 градусов, присваиваем координаты точек для мышки .... и все знаем откуда идти знаем куда идти в координатах из памяти , значит знаем угол до цели , тыкнули в нужный угол (или зажали) и пошлепали по поинтам.

OffTopic:
Белф а на какой язык ты перешел?
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Я перешел на С++
 

C2H5OH

AutoIT Гуру
Сообщения
1,473
Репутация
333
OffTopic:
С точки зрения перехода с AutoIt такое решение, на мой взгляд, имеет ряд плюсов.
Поскольку сам интерпретатор написан на С++, то, в принципе, при большом желании, можно создать библиотеки функций С++ - аналогов функций AutoIt, и писать пратически в той же привычной среде, но !
Но без проблем присущих AutoIt:
- защита кода;
- параллельные процессы;
- ООП;
- быстродействие;
...
(я не призываю переписывать функции AutoIt на С++, я оцениваю потенциал)

Однако!!!
С точки зрения стремления монетизации своих знаний - насколько востребован С++ ?
Посмотрите на те же сайты фриланса, большая часть запросов - требуется программист под андроид/iOS.
Может стоит в этом направлении двигаться?
 

lirikmel

Продвинутый
Сообщения
226
Репутация
84
OffTopic:
это лишь мое мнение и я не настаиваю к нему прислушиваться

А воопще мне кажется мы все отошли от темы , так в чем сейчас состоит "затык" с ботом ?
 

Dellroc

Осваивающий
Сообщения
151
Репутация
31
Движение пока от точки к точке
Код:
Func move($lx,$ly,$lAccuAngle=1)
	WinActivate("Drakensang Online | Эпическая онлайн-игра в стиле фентези")
	WinMove('Drakensang Online | Эпическая онлайн-игра в стиле фентези','',0,0,1000,600)
	$PID = WinGetProcess('[CLASS:Nebula3::MainWindow; INSTANCE:1]')
	$MemOpen = _MemoryOpen($PID)
	Local $iDist=0,$tDist=0,$tCTR=0;,$tHP=0
	While 1
		Local $txy=xy($PID,$MemOpen)
		Local $tCTR=be_CoordsToRotation($txy[0],$txy[1],$lx,$ly) ;угол и расстояние до цели
		Local $lRadius=150
		ToolTip('Движение к цели'&@CRLF&'Текущие X = '&$txy[0]&', Y = '&$txy[1]&' , Угол = '&$tCTR[0]&@CRLF _
		&'Заданные X = '&$lx&', Y = '&$ly&' , Дистанция = '&$tCTR[1],0,0)
		MouseMove(490 + $lRadius * Cos(($tCTR[0]-90)*4*atan(1)/180), 300 + $lRadius * Sin(($tCTR[0]-90)*4*atan(1)/180), 0) ;точка окружности
		If Not _IsPressed("01") Then MouseDown("left")

		Select
			Case $tCTR[1]<$tDist
				$iDist=0
			Case $tDist=$tCTR[1] And $iDist=10		; Застряли
				If _IsPressed("01") Then MouseUp("left")
				ToolTip('Застряли',0,0)
				Return 2
			Case $tCTR[1]>$tDist And $tDist<>0
				TrayTip('','Движение к цели'&@CRLF&'Текущие X = '&$txy[0]&', Y = '&$txy[1]&' , Угол = '&$tCTR[0]&@CRLF _
				&'Заданные X = '&$lx&', Y = '&$ly&' , Дистанция = '&$tCTR[1]&@CRLF&'Промазали мимо точки.',0,0)
				move($lx,$ly,$lAccuAngle)
		EndSelect
		If $tCTR[1] <= $lAccuAngle Then
			If _IsPressed("01") Then MouseUp("left")
			ExitLoop							; Достигли места назначения
		EndIf
;~ 		$tHP=_HP($PID,$DllInformation)
;~ 		If $tHP=0 Then
;~ 			Sleep(7000)
;~ 			$tHP=_HP($PID,$DllInformation)
;~ 			If $tHP=0 Then
;~ 				Send('{w up}')
;~ 				TrayTip('','Персонаж мёртв'&@CRLF&'HP = 0',0,2)
;~ 				Sleep(1000)
;~ 				Return 1
;~ 			EndIf
;~ 		EndIf
		$iDist=$iDist+1
		$tDist=$tCTR[1]
		Sleep(50)


	WEnd
	ToolTip('',0,0)
	Return 0
EndFunc

Код:
Func be_CoordsToRotation($bectr_X1, $bectr_Y1, $bectr_X2, $bectr_Y2)
    Local $bectr_DX = $bectr_X2-$bectr_X1
    Local $bectr_DY = $bectr_Y2-$bectr_Y1
    Local $bectr_AXY = Abs(ATan($bectr_DX/$bectr_DY))
    Local $bectr_AYX = Abs(ATan($bectr_DY/$bectr_DX))
    Local $bectr_hpi = ATan(1)*2
    Local $bectr_Angle = 0

	If $bectr_X1 > $bectr_X2 Then
        $bectr_Angle += 2*$bectr_hpi
        If $bectr_Y1 > $bectr_Y2 Then
            $bectr_Angle += $bectr_AXY
        ElseIf $bectr_Y1 = $bectr_Y2 Then
            $bectr_Angle += $bectr_hpi
        ElseIf $bectr_Y1 < $bectr_Y2 Then
            $bectr_Angle += $bectr_hpi
            $bectr_Angle += $bectr_AYX
        EndIf
    ElseIf $bectr_X1 = $bectr_X2 Then
        If $bectr_Y1 > $bectr_Y2 Then
            $bectr_Angle += 2*$bectr_hpi
        EndIf
    ElseIf $bectr_X1 < $bectr_X2 Then
        If $bectr_Y1 > $bectr_Y2 Then
            $bectr_Angle += $bectr_hpi
            $bectr_Angle += $bectr_AYX
        ElseIf $bectr_Y1 = $bectr_Y2 Then
            $bectr_Angle += $bectr_hpi
        ElseIf $bectr_Y1 < $bectr_Y2 Then
            $bectr_Angle += $bectr_AXY
        EndIf
    EndIf
	Local $R = Round(Sqrt(($bectr_X1 - $bectr_X2) ^ 2 + ($bectr_Y1 - $bectr_Y2) ^ 2),4)
	$bectr_Angle = Round($bectr_Angle*45/ATan(1),4)
	Local $lR[2] = [$bectr_Angle,$R]
    Return $lR
EndFunc

Код:
Func xy($PID,$MemOpen)
Local $lOffsetX[6]=[0,Dec(48),Dec('c4'),Dec(54),Dec(90),Dec(378)]
Local $lOffsetY[6]=[0,Dec(48),Dec('c4'),Dec(54),Dec(90),Dec(370)]
Local $lXY[2],$lt[2]
If $PID = -1 Then
	SetError(1)
    Return 0
EndIf
$lt=_MemoryPointerRead("0x"&hex(_MemoryModuleGetBaseAddress($PID, "DrakensangOnline.exe")+0x00A112B8),$MemOpen,$lOffsetX,'float')
If @error Then Return '#'
$lXY[0]=Round($lt[1]+500,3)
$lt=_MemoryPointerRead("0x"&hex(_MemoryModuleGetBaseAddress($PID, "DrakensangOnline.exe")+0x00A112B8),$MemOpen,$lOffsetY,'float')
If @error Then Return '#'
$lXY[1]=Round($lt[1]+500,3)
If @error Then Return '#'
Return $lXY
EndFunc
На свежую голову подправил первый скрипт для более лёгкого усвоения
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Да, ребят, вот это вы раздули тему, пока меня небыло :laugh:

lirikmel сказал(а):
А воопще мне кажется мы все отошли от темы , так в чем сейчас состоит "затык" с ботом ?
Несколько дней наблюдал за его работой, исправлял мелкие недочеты, сделал автоматический перезапуск игры и самого бота. Проблем несколько, одна из них в том, что я по жизни троечник по геометрии. Нет, в 3Д Макс я рисую, что такое полигональная модель знаю, чертеж начерчу практически любой сложности, а вот градусы, синусы и косинусы... Проболел, когда это дело в школе проходили, кое как списал на экзаменах решения и... Вообщем для бота слепил простой линейный алгоритм перемещения. Показываю:

Код:
; здесь $pointX и $pointY - координаты точки (поинта), в которую топаем
; $aCoords - найденные координаты перса на миникарте
Local $persPos[2] = [445,268] ; фактические координаты перса относительно окна
Local $Q = 32                 ; коэффициент, количество пикселей, добавляемых к направлению координат

 ; Вычисляем положение поинта относительно перса на миникарте:
		 Select ; определяем, в какой из четырех частей относительно осей координат находится поинт
			Case $pointX < $aCoords[0] And $pointY < $aCoords[1] ; верхняя левая
			   $Lx = $persPos[0] - ($aCoords[0] - $pointX) - $Q 
			   $Ly = $persPos[1] - ($aCoords[1] - $pointY) - $Q
			
			Case $pointX > $aCoords[0] And $pointY < $aCoords[1] ; верхняя правая
			   $Lx = $persPos[0] + ($pointX - $aCoords[0]) + $Q
			   $Ly = $persPos[1] - ($aCoords[1] - $pointY) - $Q
			
			Case $pointX < $aCoords[0] And $pointY > $aCoords[1] ; нижняя левая
			   $Lx = $persPos[0] - ($aCoords[0] - $pointX) - $Q
			   $Ly = $persPos[1] + ($pointY - $aCoords[1]) + $Q
			
			Case $pointX > $aCoords[0] And $pointY > $aCoords[1] ; нижняя правая
			   $Lx = $persPos[0] + ($pointX - $aCoords[0]) + $Q
			   $Ly = $persPos[1] + ($pointY - $aCoords[1]) + $Q
			   
			Case $pointX < $aCoords[0] And $pointY = $aCoords[1] ; левая
			   $Lx = $persPos[0] - ($aCoords[0] - $pointX) - $Q
			   $Ly = $persPos[1]
			   
			Case $pointX = $aCoords[0] And $pointY < $aCoords[1] ; верхняя
			   $Lx = $persPos[0]
			   $Ly = $persPos[1] - ($aCoords[1] - $pointY) - $Q
			   
			Case $pointX > $aCoords[0] And $pointY = $aCoords[1] ; правая
			   $Lx = $persPos[0] + ($pointX - $aCoords[0]) + $Q
			   $Ly = $persPos[1]
			   
			Case $pointX = $aCoords[0] And $pointY > $aCoords[1] ; нижняя
			   $Lx = $persPos[0]
			   $Ly = $persPos[1] + ($pointY - $aCoords[1]) + $Q
			
			Case Else ; не попал ни в одну (скорее всего перс на точке поинта)
			   Return 1
			   
		 EndSelect

Там не измеряется расстояние, задано лишь пошаговое движение до тех пор, пока перс не придет в точку. Смешно за ним наблюдать, но... Были вопросы, но сейчас смотрю алгоритмы, которые выложил Dellroc и пытаюсь в них въехать, так же покопаюсь в коде, который выложил lirikmel к игре RoyalQuest. Может найду решение задачи. А пока вопрос: у меня не получилось сделать "окно игры поверх всех". Реализовал так:

Код:
WinSetOnTop($hWnd, "", 1)


Да, окошко становится поверх остальных, но другие окна, имеющие тот же статус, его перекрывают, в результате бот сбивается. Например, антивирус радостно заорал, что он обновился - боту приходится перезагружаться. Или еще пример - TeamViewer (ну хотел со своего телефона на работе быть в курсе, чем днем бот занимается) после завершения сеанса уведомляет, что компания была спонсором этого сеанса связи :laugh: (хотел даже бота писать, чтоб сидел в памяти и ловил это чертово окошко), окно вообще поверх всего вылезает. Понимаю, что можно кликать и в неактивное окно, но вначале хочу разобраться как это делается, посмотрев исходники, но все же - как зморозить окно игры вообще поверх всего? Задача такая должна быть решена по той причине, что я не знаю, что еще там вылезет поверх окна бота. Или эта овчинка вообще выделки не стоит? Просто была идея прямо на этом окне игры рисовать какие то данные бота, выводить статистику и сообщения (при этом не нарушая работу функций поиска пикселя), практической пользы от этого я не вижу, просто для молодежи такие штучки выглядят "понтово". А мне в дальнейшей работе пригодилось бы.

Еще вопрос, который меня заинтересовал - изометрия. Есть готовые примеры и в двух словах (а лучше в трех) как это можно реализовать?
 

C2H5OH

AutoIT Гуру
Сообщения
1,473
Репутация
333
OffTopic, наверное.

Ответ #26:
Вначале накладывается сетка. Допустим 5х5 пикселей. Потом все составляющие обрабатываются. Суть в поиске аналогичного по величине участка по кривой Безье, причем не по цвету, а по размеру. На сетке строится сама кривая, гнутый вектор. Ну а потом делаешь проверку типа "на что похож этот вектор". У меня каждый такой вектор сжимался в простейшую фигуру и сравнивался с шаблоном.


Ответ #67:
Проблем несколько, одна из них в том, что я по жизни троечник по геометрии. Нет, в 3Д Макс я рисую, что такое полигональная модель знаю, чертеж начерчу практически любой сложности, а вот градусы, синусы и косинусы... Проболел, когда это дело в школе проходили, кое как списал на экзаменах решения


Чёт sebun ты того... Не того...
Сдаётся мне нет у тебя никакого кода распознавания капчи...
:scratch:
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Чёт sebun ты того... Не того...Сдаётся мне нет у тебя никакого кода распознавания капчи...
В принципе все решение здесь. Тут нет абстрактных функций. А доказывать я тебе ничего не обязан и не буду, и данный топик не для этого создавался. ;)
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Ламерский вопрос, но все же спрошу:
Код:
Opt("MouseCoordMode", 2)


Прикол в том, что у меня весь код в подключаемых файлах, в некоторых работает как задано, а в некоторых отсчет координат идет не от клиентской части, а устанавливаются абсолютные экранные координаты, хотя никаких переназначений нет. В чем может быть причина? Нужно ли в каждом файле, подключенном через #include, дублировать настройки?
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Нет не нужно, ищи баг, возможно окно теряет контроль, поэтому скрипт начинает работать от активного угла рабочего стола.
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Спасибо, Belfigor, нашел. Почему то MsgBox сбивал окно. Вот код:
Код:
MsgBox(4096, "", "Определено как: " & $currentLocation, 3)
Sleep(1000)


Убрал сообщение, все стало на свои места.
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Месседж бокс всегда сбивает фокус окна, для того чтобы юзать месседж боксы не боясь потерять фокус, тебе надо отдельную функцию, типа
Код:
Func _MsgBox($1, $2, $3, $4)
    MsgBox($1, $2, $3, $4)
    WinActivate("qwqw")
    WinWaitActive("qwqw")
EndFunc

Так же месседж бокс нормально возвращает контроль окну в том случае, если закрывается сам после указанного времени. В большинстве случаев конечно.
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Спасибо, Belfigor, я это учту в дальнейшей работе. Во всех функциях, работающих с окном, у меня стоит проверка на активность и активация, если фокус сбит. А тут как раз такой проверки небыло. Буду знать... :ok:


Добавлено:
Сообщение автоматически объединено:

45f5466e04ea.gif


А вот с этим как быть? У меня нет нет рекурсии в скрипте, это функция проверки окна, вот код:
Код:
Func CheckWinActive($hWnd)

	; Проверка существования окна
	If Not WinExists($hWnd) Then Return 0

	; Проверка активности окна
	If Not WinActive($hWnd) Then
		WinActivate($hWnd) ; пытаемся сделать Drakensang активным окном.
		If Not WinActive($hWnd) Then
			Return 0
		EndIf
	EndIf
	
	Return 1
		
EndFunc

Вызывается во всех функциях, работающих с окном. По идее должна сработать и вернуть результат. Каким образом переполняется стек, понять пока не могу, вроде весь код логичен. $hWnd объявлена как глобальная переменная и в нее записывается дескриптор окна функцией проверки окон (код этой функции большой, не буду выкладывать, там проверка наличия окна по TITLE и код, выполняемый в случае присутствия этого окна, результат - $hWnd). Если ее закомментировать, жалуется на другую функцию, стоящую перед ней. Пока сам ищу причину, но хотелось бы узнать, какое событие способствует появлению данного окна? Зацикливания какого то участка кода не обнаружил. Проблема возникает в тaком коде:

Код:
While 1
    $s = GoPoint()
WEnd

Func GoPoint()
    While 1
        CheckWinActive()
        ; выполняется код
        If $a = 1 Then ExitLoop
    WEnd
EndFunc


То есть, он жалуется на два вложенных друг в друга бесконечных цикла?
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Нет, не должен. В любом случае не видя всего кода целиком, сказать тебе никто ничего не сможет.
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
Нашел я проблему, и она оказалась крайне странной. Вот упрощенный код:

Код:
While 1
    $s = GoPoint()
WEnd

Func GoPoint()
    While 1
        CheckWinActive() ; проверка активности окна, поскольку далее маусклик
        $q = ScanMap()
        ; тут маусклик
        If $q = 1 Then ExitLoop
    WEnd
EndFunc

Func ScanMap()
    CheckWinActive() ; так же проверка, так как далее чтение пикселя
    $s = MapActive()
    ; читаем пиксели
    Return $s
EndFunc

Func MapActive()
    CheckWinActive()
   ; тут жмем на кнопки в игре, активируя карту
EndFunc

Func CheckWinActive($hWnd)

    ; Проверка существования окна
    If Not WinExists($hWnd) Then Return 0

    ; Проверка активности окна
    If Not WinActive($hWnd) Then
        WinActivate($hWnd) ; пытаемся сделать Drakensang активным окном.
        If Not WinActive($hWnd) Then
            Return 0
        EndIf
    EndIf
   
    Return 1
       
EndFunc


Как видно из этого кода, первым запускается основной цикл бота, в котором вызываются различные функции (а их там много). GoPoint - функция движения перса в указанную точку. Она в свою очередь запускает функцию ScanMap, которая должна вернуть координаты точки на карте. ScanMap в свою очередь запускает функцию проверки активности миникарты. Во всех этих функциях вызывается CheckWinActive, которая проверяет, активно ли окно игры и делает его активным в случае, если окно не активно, так как внутри этих функций производится работа с окном игры - чтение пикселей или тыканье мышкой. Каждая из этих функций является самостоятельной и может быть вызвана по отдельности, поэтому проверка активности окна стоит в каждой функции. Проблема как раз в том, что все они поочередно вызывают CheckWinActive, что и приводит к данной ошибке. Странно однако, так как по идее данный код должен работать. :stars:

Дублировать код CheckWinActive в каждой функции не хотелось бы, но, вставив вместо CheckWinActive конструкцию вида:

Код:
If Not WinActive($hWnd) Then

проблему удалось решить, однако код уже не так привлекательно выглядит (вместо одной функции теперь набор операторов). Для меня это, в принципе, не критично, но все же непонятна проблема. Автоит видит ошибку там, где ее по логике быть не должно.
 

InnI

AutoIT Гуру
Сообщения
4,951
Репутация
1,446
sebun [?]
по идее данный код должен работать
И он работает! Вот ваш код на примере калькулятора
Код:
Run("calc.exe")
$wnd = WinWait("[class:CalcFrame]")
$count = 0

While 1
    $s = GoPoint()
    ConsoleWrite("main" & @LF)
WEnd

Func GoPoint()
    While 1
        CheckWinActive($wnd) ; проверка активности окна, поскольку далее маусклик
        $q = ScanMap()
        ; тут маусклик
        If $q = 1 Then ExitLoop
    WEnd
EndFunc

Func ScanMap()
    CheckWinActive($wnd) ; так же проверка, так как далее чтение пикселя
    $s = MapActive()
    ; читаем пиксели
    Return $s
EndFunc

Func MapActive()
    CheckWinActive($wnd)
   ; тут жмем на кнопки в игре, активируя карту
EndFunc

Func CheckWinActive($hWnd)

    ; Проверка существования окна
    If Not WinExists($hWnd) Then Return 0

    ; Проверка активности окна
    If Not WinActive($hWnd) Then
        WinActivate($hWnd) ; пытаемся сделать Drakensang активным окном.
        If Not WinActive($hWnd) Then
            Return 0
        EndIf
    EndIf
    $count += 1
    ConsoleWrite($count & @LF)
    If $count = 5101 Then Exit

    Return 1

EndFunc
А теперь про сообщение. В нём говорится, что у вас превышен уровень рекурсии. Рекурсия - это вызов функции из самой себя. В AutoIt есть ограничение: максимальное число рекурсивных вызовов 5100. Вот пример, приводящий к вашей ошибке
Код:
$count = 0

test()

Func test()
  $count += 1
  ConsoleWrite($count & @LF)
  test() ; <-- вызов самой себя
EndFunc
Здесь видно, что реально число вызовов ещё меньше: 3899.
Поэтому, смотрите, что у вас находится в строке 50. Скорее всего вы вызываете CheckWinActive() внутри её самой.
 
Автор
sebun

sebun

Знающий
Сообщения
81
Репутация
5
InnI сказал(а):
Поэтому, смотрите, что у вас находится в строке 50. Скорее всего вы вызываете CheckWinActive() внутри её самой.
Спасибо, но дело не в CheckWinActive(). Дело было действительно в рекурсии. Должен извинится, что задал вопрос, детально не просмотрев весь код. Просто объем большой уже. И истиная причина была в функциях обработки миникарты, MiniMapActive() и MiniMapPos(). Одна активирует миникарту, нажимая кнопку в окне игры, вторая получает координаты персонажа. Поскольку карта имеет три положения (маленькая в углу, на весь экран, нет карты), то я сделал проверку. Если MiniMapPos() вернет координаты, значит считаем, что карта активирована, иначе жмем кнопку снова. А в MiniMapPos() первой строкой стояла активация миникарты. Вот вам и рекурсия. Я вначале комментировал код, чтобы найти проблемный участок, и закомментировал вызов MiniMapActive(), после чего стал избавляться от CheckWinActive(). Ну и написал тут, что типа все работает, если код открытым текстом, а не в виде функции. Вообщем проблему нашел и решил, спасибо вам за советы.
 

InnI

AutoIT Гуру
Сообщения
4,951
Репутация
1,446
OffTopic:
sebun [?]
Вот вам и рекурсия
Точно! Как же я забыл, что функции можно рекурсивно друг из друга вызывать :-[
И, действительно, показывает ошибку на самой "безобидной" функции :smile:
Код:
$count = 0

test1()

Func test1()
  type()
  test2()
EndFunc

Func test2()
  type()
  test1()
EndFunc

Func type()
  $count += 1
  ConsoleWrite($count & @LF)
EndFunc
 
Верх