Что нового

[Властелин Колец Онлайн] Чтение памяти игры и работа с неактивным окном

gmaxru

Новичок
Сообщения
7
Репутация
4
Добрый день.

1) Перерыл множество форумов, нигде не могу найти ответа как найти базовые адреса и офсеты (ХП, МП, координаты, угол поворота, ближайшие мобы, игроки, ресурсы) для игры Властелин Колец Онлайн. Может быть кто сталкивался с данной игрой?
Клиент можно скачать отсюда translate.lotros.ru/guides/7-gaid-po-lotro-dlja-novichkov.html

Находил динамические значения ХП через Cheat Engine, но до зеленого адреса так и не добрался. Возможно данные както шифруются. Разработчик бота pwnagebot (syphi.net/lotro) для US/EU версии игры мне ответил что используются какие-то хэш таблицы, я так и не понял что именно он имел ввиду. При этом для US/EU клиента игры они регулярно обновляют список офсетов, но данные офсеты не подходят для русской версии клиента и заниматься ими автор бота отказывается, т.к. для этого требуется скачивать отдельно русский клиент.

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

2) Пока что написал автобой без передвижений для класса Охотник с чтением состояния скиллов и бафов через PixelGetColor и в некоторых случая через чтение лога (чат, бой, ошибки без проблем сохраняются в файл и считываются оттуда в реальном времени, можно даже таким образом выводить текущие координаты командой /loc в чат, но имхо палевно это спамить эту команду несколько раз в секунду).
Сейчас занимаюсь реализацией этого для неактивного окна через WinAPI функции кликов мышью по заданным координатам и снятия скриншота неактивного окна через заданные промежутки времени (под Windows 7 x64). Пока всё работает как задумано по отдельности, но воедино еще не собрал.
 
Автор
G

gmaxru

Новичок
Сообщения
7
Репутация
4
C shade-variation для Memory Device Contexts вроде разобрался. Может быть реализовано не самым лучшим образом, но пока быстродействия хватает. Через регулярные выражения реализовывать не пытался, т.к. трудновато для моего понимания, а внятного help'a пока не нашел. Да, и к слову, перебор всех пикселей для разрешения 1920х1080 занимает ~3 секунды, но я не перебираю их все, а только в определенной области. Заметил еще лично у меня функция успешно работает и при shade-variation = 0, так что может быть её реализация была и лишней :smile: в качестве альтернативы функции GetPixel сделал свою _BitGetColor, скорость работы 0,01 сек, что в 3-6 раз быстрее.

Функция производит поиск только первого подходящего пикселя (сравниваются соседние с ним пара пикселей слева и справа), этого пока достаточно, поэтому не стал реализовывать поиск всех подходящих пикселей за один раз.

ControlClick игра не воспринимает никаким образом, поэтому для кликов мышью использую PostMessage и $WM_MOUSEMOVE, $WM_LBUTTONDOWN, $WM_LBUTTONUP, $WM_RBUTTONDOWN, $WM_RBUTTONUP.

ControlSend воспринимает, если перед ним отослать окну $WM_ACTIVATE: DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", 0x6, "int", 1, "int", 0).
ОДНАКО! После отправки игре сообщений $WM_LBUTTONDOWN или $WM_RBUTTONDOWN команда ControlSend перестает восприниматься игрой даже при предварительной отправке $WM_ACTIVATE.
Решения этой проблемы так и не нашел (пробовал $WM_SETFOCUS - тоже не помогло), поэтому для ввода символов пришлось использовать PostMessage и $WM_KEYDOWN, $WM_KEYUP.
$WM_PASTE у меня тоже не прокатывало, т.к. окно игры не имеет Control'ов.
Пробовал $WM_CHAR - не отправляет русские символы (даже при явном указании их ASCII кодов), как только не пытался (вместо них белиберда), перепробовал все способы найденные на этом форуме.

Существует ещё одна проблема изза которой работа с неактивным окном игры теряет весь смысл!
При клике мышкой путем отправки сообщений $WM_LBUTTONDOWN или $WM_RBUTTONDOWN через PostMessage в неактивное окно игры, расположенного под другими окнами, происходит следующее: в активном в данный момент окне также происходят перемещения мышью по координатам, по которым производятся клики через $WM_LBUTTONDOWN / $WM_RBUTTONDOWN в неактивном окне игры!

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


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

Код:
#RequireAdmin
#include <WinAPI.au3>
OnAutoItExitRegister("Cleanup")
Opt("TrayAutoPause", 0) ; скрипт не останавливает работу при наведении и кликах мышью на значок скрипта в системном трее
Opt("WinWaitDelay", 100) ; продолжительность паузы успешного ожидания в оконных функциях
Opt("MouseCoordMode", 0) ; относительные координаты активного окна для указателя мыши для большей совместимости кликов через Windows Messages
Opt("PixelCoordMode", 2) ; относительные координаты клиентской области активного окна для пиксельных функций
Global $Paused = False
Global Const $kgranic[6] = [0x55,0x48,0x46,0x59,0x42,0x57] ; массив ASCII кодов символов "границ"
Global Const $meshochek[5] = [0x0F124A,0x150A0F,0x453709,0x231B01,0x48340D] ; массив пяти пикселей части изображения мешочка, идущих один за одним по оси Х
; будем искать третий пиксел, затем сравнивать соседние слева направо
Global $key_ret, $key_lParam, $key_lUpParam, $user32, $coord
Global Const $WM_KEYDOWN = 0x0100, $WM_KEYUP = 0x0101, $WM_ACTIVATE = 0x0006, $WM_SETFOCUS = 0x0007
;;; вешаем хук на обработку нажатий клавиш Pause и Control+Pause
Global $hMod = _WinAPI_GetModuleHandle(0)
Global $hStub_KeyProc = DllCallbackRegister("KeyProc", "long", "int;wparam;lparam")
Global $hHook = _WinAPI_SetWindowsHookEx($WH_KEYBOARD_LL, DllCallbackGetPtr($hStub_KeyProc), $hMod, 0)
;;;
Global $handle = WinGetHandle("[CLASS:Turbine Device Class]") ; получаем хэндл окна с игрой по его классу
Global $hWidth = _WinAPI_GetClientWidth($handle)
Global $hHeight = _WinAPI_GetClientHeight($handle)

If _WinAPI_GetKeyboardLayout($handle) <> 0x0419 Then _WinAPI_SetKeyboardLayout($handle, 0x0419) ; меняем раскладку клавиатуры на русскую в окне игры, если стоит другая
WinSetState($handle, "", @SW_DISABLE) ; отключаем возможность пользовательского взаимодействия с окном
DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_ACTIVATE, "int", 1, "int", 0) ;  делаем вид что активируем окно с игрой
DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_SETFOCUS, "int", 1, "int", 0) ;  делаем вид что передаем окну фокус для пользовательского ввода

While 1
	LClick(111, 53) ; клик в начало области ввода Поиска Невостребованных трофеев

	$user32 = DllOpen("user32.dll")
	If @error Then ExitLoop
	; вводим слово "границ" посимвольно из массива ASCII кодов для поиска Мешочков стражей границ в окне Невостребованных трофеев
	For $i = 0 To UBound($kgranic)-1
		$key_ret = DllCall($user32, 'int', "MapVirtualKey", 'int', $kgranic[$i], 'int', 0)
		$key_lParam = BitOR(BitShift($key_ret[0], -16), 1)
		$key_lUpParam = BitOR($key_lParam, 0xC0000000)
		DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYDOWN, "int", $kgranic[$i], "long", $key_lParam)
		Sleep(Random(10, 150, 1))
		DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYUP, "int", $kgranic[$i], "long", $key_lUpParam)
		Sleep(Random(15, 250, 1))
	Next
	DllClose($user32)

	RClick(43, 128) ; клик ПКМ по местоположению первого найденного трофея в списке трофеев (его там может и не быть, но это проверять дольше и вовсе необязательно)
	LClick(260, 53) ; клик в конец области ввода, чтобы далее воспользоваться Backspace и стереть написанное
	; это необходимо когда надо искать названия нескольких трофеев по очереди - данный пример упрощенный вариант для одного трофея

	$user32 = DllOpen("user32.dll")
	If @error Then ExitLoop
	$key_ret = DllCall($user32, 'int', "MapVirtualKey", 'int', 0x08, 'int', 0) ; клавиша Backspace (далее жмем 10 раз (для подстраховки) чтобы стереть написанное)
	$key_lParam = BitOR(BitShift($key_ret[0], -16), 1)
	$key_lUpParam = BitOR($key_lParam, 0xC0000000)
	For $i = 1 To 10
		DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYDOWN, "int", $key_ret[1], "long", $key_lParam)
		Sleep(Random(10, 150, 1))
		DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYUP, "int", $key_ret[1], "long", $key_lUpParam)
		Sleep(Random(15, 250, 1))
	Next
	DllClose($user32)

	LClick(1166, 650) ; клик по выбранному ассисту, чтобы убрать фокус из области ввода текста, но чтобы при этом не сбросился выбранный ассист

	$user32 = DllOpen("user32.dll")
	If @error Then ExitLoop
	$key_ret = DllCall($user32, 'int', "MapVirtualKey", 'int', 0x78, 'int', 0) ; клавиша F9 для выбора в ассист следующего игрока
	$key_lParam = BitOR(BitShift($key_ret[0], -16), 1)
	$key_lUpParam = BitOR($key_lParam, 0xC0000000)
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYDOWN, "int", $key_ret[1], "long", $key_lParam)
	Sleep(Random(10, 150, 1))
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYUP, "int", $key_ret[1], "long", $key_lUpParam)
	Sleep(Random(15, 250, 1))
	$key_ret = DllCall($user32, 'int', "MapVirtualKey", 'int', 0x31, 'int', 0) ; клавиша 1 для использования скилла (хотка на хил) на игрока в ассисте, для получения общего с ним лута
	$key_lParam = BitOR(BitShift($key_ret[0], -16), 1)
	$key_lUpParam = BitOR($key_lParam, 0xC0000000)
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYDOWN, "int", 0x31, "long", $key_lParam)
	Sleep(Random(10, 150, 1))
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_KEYUP, "int", 0x31, "long", $key_lUpParam)
	Sleep(Random(15, 250, 1))
	DllClose($user32)
	Sleep(Random(2000, 3000, 1))

	$coord = _SearchPix($hWidth - 434, $hHeight - 412, $hWidth, $hHeight, $meshochek)
	If Not @error Then RClick($coord[0], $coord[1]) ; кликаем ПКМ по изображению Мешочка стражей границ, в случае если он был найден, чтобы извлечь из него Медальены стражей границ
	Sleep(Random(4000, 6000, 1))
WEnd

; снятие скриншота неактивного окна и обрезание области по указанным координатам
; последовательное заполнение созданной в памяти структуры данных $tBits пикселями (их цветами) из полученной области
; поиск пикселя заданного цвета в структуре данных $tBits
Func _SearchPix(Const $x1, Const $y1, Const $x2, Const $y2, ByRef Const $i_Color, $shvar = 0)
	Local $i_R, $i_G, $i_B, $cur_Color, $cur_R, $cur_G, $cur_B, $pix_Color, $pix_R, $pix_G, $pix_B, $a_Pixels[2]
	Local $iWidth  = $x2-$x1
	Local $iHeight = $y2-$y1
	Local $iSize = $iWidth * $iHeight
	Local $tBits = DllStructCreate('dword[' & $iSize & ']')
	Local $hDDC = _WinAPI_GetDC($handle)
	Local $hSrcDC = _WinAPI_CreateCompatibleDC($hDDC)
	Local $hSrcBitmap = _WinAPI_CreateCompatibleBitmap($hDDC, $hWidth, $hHeight)
	Local $hSrcObj = _WinAPI_SelectObject($hSrcDC, $hSrcBitmap)
	DllCall("user32.dll", "int", "PrintWindow", "hwnd", $handle, "hwnd", $hSrcDC, "int", 1)
	Local $hDestDC = _WinAPI_CreateCompatibleDC($hDDC)
	Local $hDestBitmap = _WinAPI_CreateCompatibleBitmap($hDDC, $iWidth, $iHeight)
	Local $hDestObj = _WinAPI_SelectObject($hDestDC, $hDestBitmap)
	_WinAPI_BitBlt($hDestDC, 0, 0, $iWidth, $iHeight, $hSrcDC, $x1, $y1, 0x00CC0020) ; $SRCCOPY
	_WinAPI_GetBitmapBits($hDestBitmap, 4 * $iSize, DllStructGetPtr($tBits))
	_WinAPI_SelectObject($hSrcDC, $hSrcObj)
	_WinAPI_SelectObject($hDestDC, $hDestObj)
	_WinAPI_DeleteDC($hSrcDC)
	_WinAPI_DeleteDC($hDestDC)
	_WinAPI_DeleteObject($hSrcBitmap)
	_WinAPI_DeleteObject($hDestBitmap)
	_WinAPI_ReleaseDC($handle, $hDDC)
	If $shvar <= 0 Then
	    For $i = 1 To $iSize
			$cur_Color = BitAND(DllStructGetData($tBits, 1, $i), 0x00FFFFFF)
			If BitXOR($cur_Color, $i_Color[2]) == 0 Then
				For $k = 0 to 4
					If $k <> 2 Then
						$pix_Color = $i_Color[$k]
						$cur_Color = BitAND(DllStructGetData($tBits, 1, $i+$k-2), 0x00FFFFFF)
						If BitXOR($cur_Color, $pix_Color) <> 0 Then ExitLoop
						If $k = 4 Then
							$a_Pixels[0] = Mod($i, $iWidth) - 1 + $x1
							$a_Pixels[1] = Ceiling($i / $iWidth) - 1 + $y1
							Return $a_Pixels
						EndIf
					EndIf
				Next
			EndIf
		Next
	Else
		$i_R = BitAND(BitShift($i_Color[2], 16), 0xFF)
		$i_G = BitAND(BitShift($i_Color[2], 8), 0xFF)
		$i_B = BitAND($i_Color[2], 0xFF)
		For $i = 1 To $iSize
			$cur_Color = BitAND(DllStructGetData($tBits, 1, $i), 0x00FFFFFF)
			$cur_R = BitAND(BitShift($cur_Color, 16), 0xFF)
			$cur_G = BitAND(BitShift($cur_Color, 8), 0xFF)
			$cur_B = BitAND($cur_Color, 0xFF)
			If Abs($cur_R-$i_R)<=$shvar And Abs($cur_G-$i_G)<=$shvar And Abs($cur_B-$i_B)<=$shvar Then
				For $k = 0 to 4
					If $k <> 2 Then
						$pix_Color = $i_Color[$k]
						$pix_R = BitAND(BitShift($pix_Color, 16), 0xFF)
						$pix_G = BitAND(BitShift($pix_Color, 8), 0xFF)
						$pix_B = BitAND($pix_Color, 0xFF)
						$cur_Color = BitAND(DllStructGetData($tBits, 1, $i+$k-2), 0x00FFFFFF)
						$cur_R = BitAND(BitShift($cur_Color, 16), 0xFF)
						$cur_G = BitAND(BitShift($cur_Color, 8), 0xFF)
						$cur_B = BitAND($cur_Color, 0xFF)
						If Not (Abs($cur_R-$pix_R)<=$shvar And Abs($cur_G-$pix_G)<=$shvar And Abs($cur_B-$pix_B)<=$shvar) Then ExitLoop
						If $k = 4 Then
							$a_Pixels[0] = Mod($i, $iWidth) - 1 + $x1
							$a_Pixels[1] = Ceiling($i / $iWidth) - 1 + $y1
							Return $a_Pixels
						EndIf
					EndIf
				Next
			EndIf
		Next
	EndIf
	Return SetError(1)
EndFunc

Func LClick($X = "", $Y = "") ; функция клика левой кнопкой мыши
	Local Const $WM_MOUSEMOVE = 0x0200, $WM_LBUTTONDOWN = 0x0201, $WM_LBUTTONUP = 0x0202
	Local $long = BitOR($Y * 0x10000, BitAND($X, 0xFFFF))
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_ACTIVATE, "int", 1, "int", 0) ;  делаем вид что активируем окно с игрой
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_SETFOCUS, "int", 1, "int", 0) ;  делаем вид что передаем окну фокус для пользовательского ввода
	Local $mouse_pos = MouseGetPos() ; запоминаем текущее положение указателя мыши
	$user32 = DllOpen("user32.dll")
	If @error Then Exit
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_MOUSEMOVE, "int", 0, "long", $long) ; перемещаем указатель мыши в окне игры по указанным функции координатам
	Sleep(Random(15, 25, 1))
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_LBUTTONDOWN, "int", 0x0001, "long", 0) ; производим клик в окне игры
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_LBUTTONUP, "int", 0x0001, "long", 0)
	Sleep(Random(15, 25, 1))
	DllClose($user32)
	MouseMove($mouse_pos[0], $mouse_pos[1], 0) ; перемещаем указатель мыши в предварительно записанные координаты
	Sleep(Random(25, 250, 1))
EndFunc

Func RClick($X = "", $Y = "") ; функция клика правой кнопкой мыши
	Local Const $WM_MOUSEMOVE = 0x0200, $WM_RBUTTONDOWN = 0x0204, $WM_RBUTTONUP = 0x0205
	Local $long = BitOR($Y * 0x10000, BitAND($X, 0xFFFF))
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_ACTIVATE, "int", 1, "int", 0) ;  делаем вид что активируем окно с игрой
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_SETFOCUS, "int", 1, "int", 0) ;  делаем вид что передаем окну фокус для пользовательского ввода
	Local $mouse_pos = MouseGetPos() ; запоминаем текущее положение указателя мыши
	$user32 = DllOpen("user32.dll")
	If @error Then Exit
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_MOUSEMOVE, "int", 0, "long", $long) ; перемещаем указатель мыши в окне игры по указанным функции координатам
	Sleep(Random(15, 25, 1))
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_RBUTTONDOWN, "int", 0x0001, "long", 0) ; производим клик в окне игры
	DllCall($user32, "int", "PostMessage", "hwnd", $handle, "int", $WM_RBUTTONUP, "int", 0x0001, "long", 0)
	Sleep(Random(15, 25, 1))
	DllClose($user32)
	MouseMove($mouse_pos[0], $mouse_pos[1], 0) ; перемещаем указатель мыши в предварительно записанные координаты
	Sleep(Random(25, 250, 1))
EndFunc

; функция постановки на паузу по горячей клавише Pause Break
Func TogglePause()
	AdlibUnRegister("TogglePause")
    While $Paused
		Sleep(1000)
    WEnd
	If _WinAPI_GetKeyboardLayout($handle) <> 0x0419 Then _WinAPI_SetKeyboardLayout($handle, 0x0419) ; меняем раскладку клавиатуры на русскую в окне игры, если стоит другая
	WinSetState($handle, "", @SW_DISABLE) ; отключаем возможность пользовательского взаимодействия с окном
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_ACTIVATE, "int", 1, "int", 0) ;  делаем вид что активируем окно с игрой
	DllCall("user32.dll", "int", "SendMessage", "hwnd", $handle, "uint", $WM_SETFOCUS, "int", 1, "int", 0) ;  делаем вид что передаем окну фокус для пользовательского ввода
EndFunc

; DLL Callback function
Func KeyProc($nCode, $wParam, $lParam)
	If $nCode < 0 Then Return _WinAPI_CallNextHookEx($hHook, $nCode, $wParam, $lParam)
	Local $tKEYHOOKS = DllStructCreate($tagKBDLLHOOKSTRUCT, $lParam)
	Local $tKeyCode = DllStructGetData($tKEYHOOKS, "vkCode")
	If $wParam = $WM_KEYDOWN Then
		If $tKeyCode = 0x03 Then ; код клавиш "CTRL+PAUSE"
			Exit
		ElseIf $tKeyCode = 0x13 Then ; код клавиши "PAUSE"
			$Paused = Not $Paused
			If $Paused Then
				WinSetState($handle, "", @SW_ENABLE) ; включаем возможность пользовательского взаимодействия с окном
				AdlibRegister("TogglePause", 100)
			EndIf
		EndIf
	EndIf
	Return _WinAPI_CallNextHookEx($hHook, $nCode, $wParam, $lParam)
EndFunc

Func Cleanup() ; функция срабатывающая при завершении скрипта
	_WinAPI_UnhookWindowsHookEx($hHook)
	DllCallbackFree($hStub_KeyProc)
	WinSetState($handle, "", @SW_ENABLE) ; включаем возможность пользовательского взаимодействия с окном
EndFunc

Func _WinAPI_LoadKeyboardLayout($iLanguage, $iFlag = 0)
	Local $Ret = DllCall('user32.dll', 'uint_ptr', 'LoadKeyboardLayoutW', 'wstr', Hex($iLanguage, 8), 'uint', $iFlag)
	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, 0)
	EndIf
	Return $Ret[0]
EndFunc

Func _WinAPI_SetKeyboardLayout($hWnd, $iLanguage, $iFlags = 0)
	Local $hLocale = 0
	If $iLanguage Then
		$hLocale = _WinAPI_LoadKeyboardLayout($iLanguage)
		If @error Then
			Return SetError(1, 0, 0)
		EndIf
	EndIf
	DllCall('user32.dll', 'none', 'SendMessage', 'hwnd', $hWnd, 'uint', 0x0050, 'uint', $iFlags, 'uint_ptr', $hLocale)
	Return 1
EndFunc

Func _WinAPI_GetKeyboardLayout($hWnd)
	Local $Ret
	$Ret = DllCall('user32.dll', 'dword', 'GetWindowThreadProcessId', 'hwnd', $hWnd, 'ptr', 0)
	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, 0)
	EndIf
	$Ret = DllCall('user32.dll', 'uint_ptr', 'GetKeyboardLayout', 'dword', $Ret[0])
	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, 0)
	EndIf
	Return $Ret[0]
EndFunc

Func _WinAPI_GetBitmapBits($hBitmap, $iSize, $pBits)
	Local $Ret = DllCall('gdi32.dll', 'dword', 'GetBitmapBits', 'ptr', $hBitmap, 'dword', $iSize, 'ptr', $pBits)
	If (@error) Or (Not $Ret[0]) Then
		Return SetError(1, 0, 0)
	EndIf
	Return $Ret[0]
EndFunc
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
По поводу памяти. То же самое например было у меня для FF XIV. Динамический адрес находится на раз-два. А вот дальше если майнить в глубь ручками - результат нулевой. Посмотри на ютубе инструкции к CE Pointer Scanner, так же возможно придется приегнуть чуть-чуть к дизассемблированию. С адресами камеры всё так же. Нужно её крутить, смотреть что меняется и искать закономерности. С ресами, мобами и прочими группами объектов все гораздо сложнее т.к. тебе уже надо будет искать не указатели на единичное значение, а указатели на начало структур данных.
 
Автор
G

gmaxru

Новичок
Сообщения
7
Репутация
4
Сколько ни садился за изучение ассемлера дальше прочтения нескольких страниц теории Юрова так и не продвинулся)) хотя мне очень симпатизируют работы Криса Касперски в этой области и дома лежит ряд его книг, но они для уже продвинутых личностей ) пытался читать, ничего не понял, хотя очень интересно )) То ли дело AutoIt. Второй, после Pascal'я и QBasic'a, язык который мне легко дается. Даже LUA для меня тёмный лес )
Может быть можно попробовать выдернуть список оффсетов для pwnagebot (http://syphi.net/lotro/) - они хранятся на сервере и обновляются при обновлении клиента и поискать аналогичные для русской версии игры?

По сабжу, обновил второй пост. Переписал скрипт с учетом всей глючности кликов мышью средствами Windows Messages. Собственно теперь перед кликом мышью в окне игры курсор запоминает свое местоположение и возвращается в него после клика - всё же это лучше чем ничего ))

Добавил горячие клавиши для паузы и завершения скрипта. Само собой HotKeySet в игре не пашет, поэтому пришлось использовать хуки. Чтобы не сбивалось перемещение мышкой в окне с игрой во время работы скрипта добавил @SW_DISABLE. В связи с этим пришлось всё-таки использовать $WM_ACTIVATE и $WM_SETFOCUS, иначе клики мышкой и нажатия клавиш не проходят.
Смена раскладки клавиатуры на русский язык в окне с игрой, чтобы вводились именно русские символы посредством $WM_KEYDOWN, автоматизирована.

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

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Не, ты не понял. Никто не просит тебя знать ассемблер для работы с Pointer Scanner. Посмотри пару видосов на ютубе. Все что ты делаешь это несколько циклов монотонных телодвижений с программой. По окончании имеешь список вероятных базовых адресов. Я не спорю, если знать ассемблер, то ты можешь листать асм код, как например исходник автоита и понимать "ху из ху". Но неужели ты думаешь что на этом форуме много кто владеет такими познаниями? Я бы даже больше сказал, те кто владеют такими познаниями тут не сидят и переходят на следующие уровни программирования. Но тем не менее оффсеты то мы находим :smile:.

P.S. Знания ассемблера могут помочь только в очень очень очень сложной ситуации с алгоритмом вычисления адресов расположения данных самим клиентом. В 90% игр поинтерсканнер это over9000 необходимого.
 
Автор
G

gmaxru

Новичок
Сообщения
7
Репутация
4
Belfigor, спасибо, получилось найти оффсеты для текущего и максимального ХП персонажа и текущего ассиста. Только нашлось их по 6-8 штук. Указывают на один и тот же адрес, только смещения разные. Можно брать любой из них я так понимаю.
Парсить лог чата чтото не выходит пока. Вот бы еще найти откаты скиллов, чтобы к ним привязаться для определения готов скилл к использованию или еще в откате (привязка к кулдауну скилла не катит, т.к. после использования некоторых умений другие скиллы откатываются). На первое время этого было бы за глаза. С нахождением текущих координат думаю проблем не будет.

В функции _SearchPix была ошибка, поправил.

Еще одна проблемка нарисовалась с хуками. Если использовать хуки на глобальные клавиши, то они мешают выполнению других макросов AutoHotKey или A4Tech, где используется отправка клавиш без задержки в активное окно. Игра начинает глючить, клавиши прожимаются через раз, а курсор мыши после десятка кликов уже кликает сам по себе и моргает. Изза этого пришлось отказаться от хуков.
 

Belfigor

Модератор
Локальный модератор
Сообщения
3,608
Репутация
941
Чат искать достаточно просто. Просто вбивая в него свои сообщения и отыскивая их в памяти. Другое дело что в отличии от ХП и МП, являющихся конкретными адресами (ячейками памяти). Чат - это структура памяти и ссылаться тебе надо уже будет не на конкретный адрес, а на начало структуры, узнавать её длинну, длинну каждого сегмента и таким образом парсить чат.
 
Автор
G

gmaxru

Новичок
Сообщения
7
Репутация
4
Написал программку для измерения скорости передвижения в игре, в случае необходимости добавить прочий функционал будет не трудно.
Другое дело что пока не знаю как найти офсеты кулдаунов скиллов, мобов/игроков/предметов, чата. Пытаюсь разобраться.

KA7nnxwZ_E8.jpg


Код:
#RequireAdmin
Opt("TrayAutoPause", 0)
Opt("GUICloseOnESC", 1)
MsgBox(4096, "Жду активации игрового окна!")
Global $hwnd = WinWaitActive("[CLASS:Turbine Device Class]")
Global $pid = WinGetProcess($hwnd)
Global $lotroclient
Local $aprocesslist = ProcessList()
If @error Then Exit
For $i = 1 To $aprocesslist[0][0]
	If $aprocesslist[$i][1] = $pid Then
		$lotroclient = $aprocesslist[$i][0]
	EndIf
Next
Global $heading, $xpos, $ypos, $zpos, $oxpos, $oypos, $lxpos, $lypos, $playerx, $playery, $distancepersec
Global $baseaddr = _memorymodulegetbaseaddress($pid, $lotroclient)
Global Const $offseth[2] = [0, Dec("424")]
Global Const $addrh = "0x" & Hex($baseaddr + Dec("018839F0"))
Global Const $addrox = "0x" & Hex($baseaddr + Dec("01619FA0"))
Global Const $addroy = "0x" & Hex($baseaddr + Dec("01619FA4"))
Global Const $addroz = "0x" & Hex($baseaddr + Dec("01619FA8"))
Global Const $addrlx = "0x" & Hex($baseaddr + Dec("01619F98"))
Global Const $addrly = "0x" & Hex($baseaddr + Dec("01619F99"))
Global $maingui = GUICreate("LotRO Speed Tester by MAXIQ", 150, 200)
GUICtrlCreateLabel("Speed", 15, 15, 50, 20)
Global $testspeedlabel = GUICtrlCreateLabel("0", 65, 15, 50, 20)
GUICtrlCreateLabel("m/s", 110, 15, 25, 20)
GUICtrlCreateLabel("Direction", 15, 35, 50, 20)
Global $headinglabel = GUICtrlCreateLabel("Heading", 65, 35, 50, 20)
GUICtrlCreateLabel("X", 15, 55, 25, 20)
Global $xlabel = GUICtrlCreateLabel("XPos", 40, 55, 50, 20)
Global $pxlabel = GUICtrlCreateLabel("PXPos", 95, 55, 60, 20)
GUICtrlCreateLabel("W", 125, 55, 25, 20)
GUICtrlCreateLabel("Y", 15, 75, 25, 20)
Global $ylabel = GUICtrlCreateLabel("YPos", 40, 75, 50, 20)
Global $pylabel = GUICtrlCreateLabel("PYPos", 95, 75, 60, 20)
GUICtrlCreateLabel("S", 125, 75, 25, 20)
GUICtrlCreateLabel("Z", 15, 95, 25, 20)
Global $zlabel = GUICtrlCreateLabel("ZPos", 40, 95, 50, 20)
GUICtrlCreateLabel("ox", 15, 115, 25, 20)
Global $oxlabel = GUICtrlCreateLabel("oXPos", 40, 115, 50, 20)
GUICtrlCreateLabel("oy", 15, 135, 25, 20)
Global $oylabel = GUICtrlCreateLabel("oYPos", 40, 135, 50, 20)
GUICtrlCreateLabel("lx", 15, 155, 25, 20)
Global $lxlabel = GUICtrlCreateLabel("lXPos", 40, 155, 50, 20)
GUICtrlCreateLabel("ly", 15, 175, 25, 20)
Global $lylabel = GUICtrlCreateLabel("lYPos", 40, 175, 50, 20)
GUISetState(@SW_SHOW)
WinSetOnTop($maingui, "", 1)
While 1
	$postimer = TimerInit()
	getpositiondata()
	$previousx = $xpos
	$previousy = $ypos
	$previousz = $zpos
	slp(980)
	getpositiondata()
	$distancepersec = Round(Sqrt(($xpos - $previousx) ^ 2 + ($ypos - $previousy) ^ 2 + ($zpos - $previousz) ^ 2), 4)
	GUICtrlSetData($testspeedlabel, $distancepersec)
WEnd

Func slp($slptime)
	$slptimer = TimerInit()
	While TimerDiff($slptimer) < $slptime
		Sleep(5)
		Switch GUIGetMsg()
			Case -3
				Exit
		EndSwitch
	WEnd
EndFunc

Func getpositiondata()
	$openmem = _memoryopen($pid)
	If @error Then Return
	$mempointer = _memorypointerread($addrh, $openmem, $offseth, "float")
	$heading = $mempointer[1]
	GUICtrlSetData($headinglabel, Round($heading, 2))
	$oxpos = _memoryread($addrox, $openmem, "float")
	$oypos = _memoryread($addroy, $openmem, "float")
	$zpos = _memoryread($addroz, $openmem, "float")
	$lxpos = _memoryread($addrlx, $openmem, "ubyte")
	$lypos = _memoryread($addrly, $openmem, "ubyte")
	$xpos = $lxpos * 160 + $oxpos
	$ypos = $lypos * 160 + $oypos
	$lxpos = (8 * $lxpos) + ($oxpos / 20)
	$lypos = (8 * $lypos) + ($oypos / 20)
	$playerx = ($xpos - 29360) / 200
	$playery = ($ypos - 24880) / 200
	_memoryclose($openmem)
	GUICtrlSetData($oxlabel, Round($oxpos, 2))
	GUICtrlSetData($oylabel, Round($oypos, 2))
	GUICtrlSetData($lxlabel, Round($lxpos, 2))
	GUICtrlSetData($lylabel, Round($lypos, 2))
	GUICtrlSetData($xlabel, Round($xpos, 2))
	GUICtrlSetData($ylabel, Round($ypos, 2))
	GUICtrlSetData($pxlabel, Round(Abs($playerx), 2))
	GUICtrlSetData($pylabel, Round(Abs($playery), 2))
	GUICtrlSetData($zlabel, Round($zpos, 2))
EndFunc

Func _memoryopen($iv_pid, $iv_desiredaccess = 2035711, $if_inherithandle = 1)
	If NOT ProcessExists($iv_pid) Then
		SetError(1)
		Return 0
	EndIf
	Local $ah_handle[2] = [DllOpen("kernel32.dll")]
	If @error Then
		SetError(2)
		Return 0
	EndIf
	Local $av_openprocess = DllCall($ah_handle[0], "int", "OpenProcess", "int", $iv_desiredaccess, "int", $if_inherithandle, "int", $iv_pid)
	If @error Then
		DllClose($ah_handle[0])
		SetError(3)
		Return 0
	EndIf
	$ah_handle[1] = $av_openprocess[0]
	Return $ah_handle
EndFunc

Func _memoryread($iv_address, $ah_handle, $sv_type = "dword")
	If NOT IsArray($ah_handle) Then
		SetError(1)
		Return 0
	EndIf
	Local $v_buffer = DllStructCreate($sv_type)
	If @error Then
		SetError(@error + 1)
		Return 0
	EndIf
	DllCall($ah_handle[0], "int", "ReadProcessMemory", "int", $ah_handle[1], "int", $iv_address, "ptr", DllStructGetPtr($v_buffer), "int", DllStructGetSize($v_buffer), "int", "")
	If NOT @error Then
		Local $v_value = DllStructGetData($v_buffer, 1)
		Return $v_value
	Else
		SetError(6)
		Return 0
	EndIf
EndFunc

Func _memoryclose($ah_handle)
	If NOT IsArray($ah_handle) Then
		SetError(1)
		Return 0
	EndIf
	DllCall($ah_handle[0], "int", "CloseHandle", "int", $ah_handle[1])
	If NOT @error Then
		DllClose($ah_handle[0])
		Return 1
	Else
		DllClose($ah_handle[0])
		SetError(2)
		Return 0
	EndIf
EndFunc

Func _memorypointerread($iv_address, $ah_handle, $av_offset, $sv_type = "dword")
	If IsArray($av_offset) Then
		If IsArray($ah_handle) Then
			Local $iv_pointercount = UBound($av_offset) - 1
		Else
			SetError(2)
			Return 0
		EndIf
	Else
		SetError(1)
		Return 0
	EndIf
	Local $iv_data[2], $i
	Local $v_buffer = DllStructCreate("dword")
	For $i = 0 To $iv_pointercount
		If $i = $iv_pointercount Then
			$v_buffer = DllStructCreate($sv_type)
			If @error Then
				SetError(@error + 2)
				Return 0
			EndIf
			$iv_address = "0x" & Hex($iv_data[1] + $av_offset[$i])
			DllCall($ah_handle[0], "int", "ReadProcessMemory", "int", $ah_handle[1], "int", $iv_address, "ptr", DllStructGetPtr($v_buffer), "int", DllStructGetSize($v_buffer), "int", "")
			If @error Then
				SetError(7)
				Return 0
			EndIf
			$iv_data[1] = DllStructGetData($v_buffer, 1)
		ElseIf $i = 0 Then
			DllCall($ah_handle[0], "int", "ReadProcessMemory", "int", $ah_handle[1], "int", $iv_address, "ptr", DllStructGetPtr($v_buffer), "int", DllStructGetSize($v_buffer), "int", "")
			If @error Then
				SetError(7)
				Return 0
			EndIf
			$iv_data[1] = DllStructGetData($v_buffer, 1)
		Else
			$iv_address = "0x" & Hex($iv_data[1] + $av_offset[$i])
			DllCall($ah_handle[0], "int", "ReadProcessMemory", "int", $ah_handle[1], "int", $iv_address, "ptr", DllStructGetPtr($v_buffer), "int", DllStructGetSize($v_buffer), "int", "")
			If @error Then
				SetError(7)
				Return 0
			EndIf
			$iv_data[1] = DllStructGetData($v_buffer, 1)
		EndIf
	Next
	$iv_data[0] = $iv_address
	Return $iv_data
EndFunc

Func _memorymodulegetbaseaddress($ipid, $smodule)
	If NOT ProcessExists($ipid) Then Return SetError(1, 0, 0)
	If NOT IsString($smodule) Then Return SetError(2, 0, 0)
	Local $psapi = DllOpen("psapi.dll")
	Local $hprocess
	Local $permission = BitOR(2, 1024, 8, 16, 32)
	If $ipid > 0 Then
		Local $hprocess = DllCall("kernel32.dll", "ptr", "OpenProcess", "dword", $permission, "int", 0, "dword", $ipid)
		If $hprocess[0] Then
			$hprocess = $hprocess[0]
		EndIf
	EndIf
	Local $modules = DllStructCreate("ptr[1024]")
	Local $acall = DllCall($psapi, "int", "EnumProcessModules", "ptr", $hprocess, "ptr", DllStructGetPtr($modules), "dword", DllStructGetSize($modules), "dword*", 0)
	If $acall[4] > 0 Then
		Local $imodnum = $acall[4] / 4
		Local $atemp
		For $i = 1 To $imodnum
			$atemp = DllCall($psapi, "dword", "GetModuleBaseNameW", "ptr", $hprocess, "ptr", Ptr(DllStructGetData($modules, 1, $i)), "wstr", "", "dword", 260)
			If $atemp[3] = $smodule Then
				DllClose($psapi)
				Return Ptr(DllStructGetData($modules, 1, $i))
			EndIf
		Next
	EndIf
	DllClose($psapi)
	Return SetError(-1, 0, 0)
EndFunc


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

Кому интересно можем вместе поработать над реализацией совместных проектов и задумок.
 

Belfigor

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

gmaxru

Новичок
Сообщения
7
Репутация
4
Любимая (и единственная) игра :smile: наработок много, как и идей, но и недоработок ещё больше, если б был ещё более упорный, наверняка давно был смог их реализовать в виде полноценного универсального бота, но собрать всё воедино как-то руки не доходят. На сайте проекта русификации можно найти мой гайд по игре (руофф умер, осталась только us/eu версия, но русских игроков там много играет).

Что касается работы с памятью, с отладкой и дизассемблированием я так и не разобрался, но и не сдался, пытаюсь найти на это время и силы.
В CE достаточно найти одну из координат, остальные в памяти рядом.
HP/MP тоже рядом друг с другом, но с ними бывают странности, после релога в меню выбора персонажа оффсеты могут меняться на другие
С ассистом проблем нет, можно получить координаты, МП/ХП, кроме наименований мобов - похоже что они привязаны к каким-то ID, хранящимися в файлах ресурсов
Всё это легко находится через CE Pointer Scanner, значения в основном типа float

Чат можно парсить обыкновенно из файла путём логирование чата в игре, как вытащить его из памяти так и не смог понять.
 

britanec74

Знающий
Сообщения
22
Репутация
8
Давно не заходил на этот форум, решил зайти и тут сразу тема в которой человек 4 года не бросает свое дело. Похвально. Даже сам решил скачать игру и попробовать что либо найти.
Если нужны смещения для хп/мп персонажа, то вот они:
ХП: (lotroclient.exe+18CD46C)+24+C+9C+0+0+30+34
МП: (lotroclient.exe+18CD46C)+24+C+9C+0+4+34

Для быстрого поиска базы можно использовать массив байт функции с первым смещением.
Массив: 55 56 57 8B F9 8B 77 ?? 33 ED 90 85 F6 76 21 8B 47 ??
1. Ищем этот массив. Найдется 1 адрес - (lotroclient.exe+583F45), это на данный момент. Это адрес функции.
2. Смотрим его в отладчике (CTRL+D). Если посмотреть чуть ниже (адрес + F) там будет инструкция - mov eax,[edi+24] (24-это первое смещение от базы).
3. Ставим на эту инструкцию бряк (F5) и видим адрес записанный в edi (3AAD2800)
4. Ищем этот адрес (3AAD2800) и получаем тучу адресов, в которых всего 1 зеленый адрес. Это и есть база.

Таким методом можно легко обновлять базы)
 
Верх