20111221

Трейнеры, снова.

Я опять слегка загружен на работе, так что сегодня вечером постараюсь выложить следующее видео, а пока что ещё немного поканифолю вам, дорогие мои читатели, ваши светлые головы. Собственно, о чём сказать-то хочется? "Hello, world!", минимальную программу, умеющую говорить "Привет!" и радостно закрываться, мы более-менее написали и проанализировали. Скажу по секрету - для трейнера нам придётся написать нечто посолиднее. Давайте вспомним алгоритм работы простейшего трейнера, о котором я упомянул в [предыдущей] статье. Вот он:

1. Запуститься, показать своё окно
2. Ждать нажатия на кнопку от пользователя
3. Если пользователь всё-таки это сделал - включить (или выключить) какую-то из опций-читов
4. Корректно завершить работу после закрытия


Теперь неспеша (заварив себе ещё чашечку чая, да взяв тех французских булок™) разберём его, начиная с первого пункта. Нам нужно запуститься и показать окно трейнера. Простого MessageBox тут недостаточно, потому что оно закроется сразу после нажатия кнопки "ОК" и мы ничего с этим поделать не можем. Нужно что-то другое. К счастью, хитрые разработчики Microsoft это самое "другое" уже придумали - мы можем сделать окно! Да не просто окно, а диалоговое окно. Оно отличается от обычного окна тем, что очень хорошо умеет работать со всякими кнопочками и менюшками (а кнопочки у нас в трейнере ещё как будут), ну и чтобы создать его нужно написать меньше кода, чем для создания просто окна.


Вот что нам понадобится:


Для начала - исходный код и скомпилированное приложение, которые можно скачать по [этой ссылке]. Качаем, запускаем, читаем дальше и разбираемся.


1. Сразу после старта трейнера, получить handle (хэндл) нашего трейнера. Хэндл - суть идентификатор (номер) исполняемого файла, чтобы винда не перепутала его с какой-нибудь другой программой. Нужен он нам для того, чтобы сотворить от имени нашей программы окно (в нашем случае - диалоговое). Вот как это выглядит:



invoke GetModuleHandle,0
invoke DialogBoxParam,eax,37,HWND_DESKTOP,DialogProc,0
invoke ExitProcess,0

Значит, ведём пальчиком по строчкам или подкладываем линейку. Первая строчка - функция [GetModuleHandle], принимающая один аргумент - ноль. Служит она для получения того самого хэндла какого-то исполняемого файла, а если в аргументах передаётся ноль, то возвращает она хэндл того исполняемого файла, который её вызвал. Результат после вызова функции запихивается в регистр еах. Вроде бы, ничего сложного? Идём дальше. :)

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

Круто? Круто! Но сложновато. Не паникуйте, эту штуку нам придётся написать один раз и забыть о ней - тут менять мы ничего не будем. Сейчас я напишу что-нибудь про третью строчку (которая просто завершает процесс трейнера), а потом вернёмся дальше ко второй - в частности, к процедуре поведения нашего диалогового окна. В-ж-ж-ж-ж-ж-ж!.. Вот, возвращаюсь. :)

Итак, хэндл получили, диалоговое окно вызывали, а когда оно завершило работу - завершаем вслед за ним и работу процесса. Всё просто. Но самое интересное - это процедура работы диалогового окна.

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

-Нажали на крестик сверху-справа - диалоговое окно говорит - "Я ща закроюсь!11", послав сообщение о попытке закрытия. Винда ему отвечает - "Окей, валяй!". Оно берёт и закрывается. 
-Тыкнули в окне левой (или правой) кнопкой мыши - диалоговое окно и говорит - "Меня тыкают! Щекотно! Что делать?". И если мы опишем, что делать, оно что-нибудь сделает.

Удобно? Удобно. Делать нам нужно не много - проверять, не нажал ли пользователь какую-нибудь кнопку на клавиатуре, да уметь закрываться. Вот, собственно, простейшая процедура этого самого диалогового окна:



proc DialogProc hwnddlg,msg,wparam,lparam
        cmp     [msg],WM_CLOSE
        je      .wmclose
        xor     eax,eax
        jmp     .finish
  .wmclose:
        invoke  EndDialog,[hwnddlg],0
  .finish:
        ret
endp


Начнём с того, что процедура [DialogProc] описана в MSDN. Она принимает аж четыре аргумента - хэндл диалога, в котором крутится (ох уж эти хэндлы!), сообщения, которые ей шлёт созданный диалог и два дополнительных параметра, в которых иногда хранится дополнительная информация (каменты!) к приходящим сообщениям. По сути, эта процедура для диалога - она как мозги. Диалог только паникует и орёт, что с ним происходит, а процедура всё это выслушивает и что-то делает. В случае успеха - возвращает ноль, в случае неуспеха - 1. Теперь давайте построчно.


"proc DialogProc hwnddlg,msg,wparam,lparam" - начало процедуры, её имя и описание. Слово proc - сокращение от Procedure (англ. Процедура) - говорит нам о том, что мы пишем процедуру. Дальше - имя и параметры, которые она принимает.


cmp [msg],WM_CLOSE


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


je .wmclose


Мы говорим, переходить нам к завершению процедуры (и закрытию диалога), или же идти дальше. Напомню, je значит Jump if Equal (англ. Прыгнуть, если равно). ".wmclose" - это специальная метка, говорящая о том, где лежит код закрытия. Получается, что если сообщение будет равно WM_CLOSE - мы будем выполнять код, лежащий после метки ".wmclose".


Если же нет - переходим к следующим двум строчкам:



xor eax,eax
jmp .finish


Первой строчкой мы помещаем (то же самое, что и mov eax,0) в регистр еах нолик, говорящий о том, что всё хорошо, а дальше переходим на метку завершения работы процедуры (не диалога!). Причём в этом случае прыжок идёт безусловный - мы ничего ни с чем не сравниваем. Просто еах = 0 - и на выход!


Остаётся вот этот кусок:



.wmclose:
        invoke EndDialog,[hwnddlg],0
.finish:
        ret
endp


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


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


Последнее - endp - обозначает конец описания процедуры. Служебная конструкция, типа. 




Вот и выходит, что диалог создаётся, у него есть простенький мозг - процедура, которая обрабатывает его сообщения. И обрабатывать она умеет (пока что) только одно - сообщение о выходе. А это Alt+F4 и крестик сверху-справа.


Ребята, в исходном коде, который приложен в начале статьи, есть ещё пара непонятных моментов. В самом начале исходника:


format PE GUI 4.0
Эта строчка указывает компилятору, что мы хотим от него получить именно EXE-файл под Windows и ничего другого. Про сам формат (PE - Portable Executable) мы всё выясним чуть позже, сейчас нам о нём следует знать только пару вещей, а именно, что он делится на секции. В нашей программе их будет аж три - секция кода и секция данных, которая будет делиться на данные для импорта и секцию ресурсов. Вот как это выглядит:


Сначала - секция кода. Мы об этом компилятору говорим вот такой строкой:


section '.text' code readable executable
Секция такая-то, а последние три слова говорят, что это код, который можно как читать, так и выполнять.


Идём дальше. Секция импорта (сразу напишу её содержимое):



section '.idata' import data readable writeable
  library kernel,'kernel32.DLL',\
 user,'user32.DLL'
  import kernel,\
GetModuleHandle,'GetModuleHandleA',\
ExitProcess,'ExitProcess'
  import user,\
DialogBoxParam,'DialogBoxParamA',\
EndDialog,'EndDialog'



В этой секции описываются те внешние библиотеки, которые программа должна подключить себе для корректной работы. Корректная работа заключается в доступе к тем самым WinAPI-функциям, да-да. Как видите, мы подключили две библиотеки ("kernel32.dll" и "user32.dll"), указали, какие функции хотим использовать, после чего просто вызывали их из кода.


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


Остаётся секция ресурсов. Вот она:



section '.rsrc' resource data readable
  directory RT_DIALOG,dialogs
  resource dialogs,\
  37,LANG_ENGLISH+SUBLANG_DEFAULT,demonstration
  dialog demonstration,' DialogBox Example ',70,70,190,175,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
  enddialog


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


В секции ресурсов из нашего примера описывается одно-единственное диалоговое окно, у которого будет заголовок "DialogBox Example" и, в общем-то, сам системный заголовок (синяя блямба в верхней его части и кнопкой с крестиком справа).


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



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









3 комментария:

  1. "исходный код и скомпилированное приложение" нерабочая ссылка )) плз перезалей очень хочу научится))

    ОтветитьУдалить
  2. Та же просьба, все круто но хотелось бы глянуть исходники)

    ОтветитьУдалить
  3. Смотрю автора пока нет, вот переписал код из видео, может кому пригодится https://docs.google.com/file/d/0B495UHXsZzMUQ203YUtVVDdDUUE/edit?usp=sharing

    ОтветитьУдалить

Не люблю мат и низкий уровень грамотности. Чем конкретнее поставите свой вопрос и чем лучше он будет выглядеть - тем большая вероятность на мой ответ. :)