20120715

Продолжаем бороться с DMA.

Или встречайте [новый видеоурок]!

Демонстрирую небольшую фишку, когда надо "заморозить" значение, а указатель искать лень или не получается. Ещё, кстати, код отлаживаем в OllyDbg - вдруг кому-то кроме меня интересен этот замечательный отладчик.

Ну и небольшую фишку по ассемблерным скриптам в СЕ тоже покажу.

20120710

Чтобы не скучать.

Чуваки, пока я продолжаю разбираться с D3D, чтобы не было сильно скучно - попробуем сломать что-нибудь эдакое, нестандартное. Мне вот очень хочется углубиться в тему игрового ИИ - поведения врагов, например.

С СЕ мы уже более-менее разобрались, так что отлаживать я буду в основном в Olly - это не страшно, т.к. отладчики все похожие, но мне просто так удобнее.

Может быть, кстати, покажу ещё пару фич СЕ, про которые возможно забыл.

Если что - в комментариях пишем, какую бы опцию хотелось (помимо стандартных бессмертий и всяких бесконечных патронов) - попробую сделать.

20120704

Продолжаем болтать про графику и D3D.

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

Есть некоторый интерфейс (назовём его набором функций) для отрисовки графики. Командуешь ему команды - он выполняет. Если представить абстрактно, то выглядит это примерно так:


На картинке буковка "А" - это наша программа, "В" - это видеокарта+монитор, а D3D в симпатичном овале выполняет роль сломанного телефона - наша программа не умеет работать с железками, но может об этом попросить D3D.

Одна проблема - на всех одной D3D не хватит, поэтому каждая программа, его использующая, грузит себе (в своё адресное пространство) свою собственную:


Тут, тащемта, 0x40000 - точка входа нашей программы (с неё весь её код начинается), а по адресу 0x70000 (для примера, ибо на самом деле адрес выбирается исходя из настроения Билла Гейтса и фазы Сатурна) грузится наша личная копия D3D.

Так, библиотеку в своё адресное пространство загрузили. А что нужно программе сделать, чтобы нарисовать что-нибудь на экран? Инициализацию! Нужно вызвать специальную штуку, чтобы D3D проснулся и понял, что мы хотим рисовать: В этом нам поможет хитрая функция [CreateDevice], которая после своего выполнения предоставляет нам указатель. Что за указатель, да? Я тоже сначала ни черта не понимал, да и сейчас понимаю плохо. Если очень тупо и так, как это представляю себе я, то возвращают нам указатель на виртуальный объект, представляющий собой нашу видеокарту+монитор. У него есть список функций, примерно так:


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

call [pDevice+0x456]

Типа, взяли указатель на наш видеоадаптер, прибавили смещение до нужной нам функции и это дело вызвали. Такая вот хитрожопая конструкция называется таблицей виртуальных функций. И когда мы хотим вызвать ту же EndScene() или BeginScene(), то мы пишем так (C#):

pDevice.EndScene();

А на самом деле происходит так:

call [pDevice+endSceneOffset]

Круто? Круто!

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

BeginScene();
DrawEverything();
EndScene();

Типа, начали рисовать, нарисовали всё, закончили рисовать. И опять начали. И так далее.

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

HookedEndScene()
{
   DrawMenu();
   EndScene();
}

Типа, игра лезет за EndScene(), натыкается на нашу подлянку, наша подлянка рисует меню (DrawMenu()), а затем вызывает оригинальную EndScene().

Такая вот штука называется Хук (англ. Hook). Если задуматься, то это по смыслу очень похоже на инъекцию кода.

Собственно, наш план - получить адрес оригинальной EndScene() в адресном пространстве процесса игры (потому что именно в него грузится копия d3d.dll), заменить её на подлянку. Как получить адрес? Берём и создаём новый девайс (типа инициализироваться хотим):

CreateDevice();

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

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

Дописываем наш трейнер на C#. Инъекции кода. Часть 2.

Вторая часть будет в качестве небольшого дополнения к [первой]. Почему небольшой? Да потому, что для динамического выделения памяти нам нужно изменить в коде одну строчку. Нам понадобится новая WinAPI-функция - [VirtualAllocEx]:

В общем-то всё, что она делает - принимает в аргументах хэндл процесса, размер выделяемого куска памяти в байтах, ну и пару флагов, определяющих способ выделения и нужные права. Возвращает она что? Правильно! Адрес выделенного куска, куда уже можно записывать.

Так что мы просто берём и значение переменной caveAddress, хранящей адрес нашего кейва, присваиваем нужный вызов:

caveAddress = (Int32)(VirtualAllocEx(handle,IntPtr.Zero,(uint)(valueon.Length+5),AllocationType.Commit,MemoryProtection.ExecuteReadWrite));


Соответственно, выделить нам нужно память размером с наш скрипт + ещё пять байт для прыжка обратно в код игры (1 байт для jmp и 4 - для адреса). Штучка (Int32) нужна для конвертации в нужный тип данных (по-умолчанию функция возвращает IntPtr), иначе нам будет не посчитать буфер для записи.

Всё, что нам потребуется после этого (когда мы захотим отключить функцию трейнера) - это:

1. Записать старую инструкцию на место (value off -> addressFrom).
2. Очистить выделенную под кейв память при помощи [VirtualFreeEx].

Как пользоваться вторым пунктом (а заодно - что нужно для записи) - узнаём на [pinvoke.net]. На всякий случай скажу, что для очистки нам нужен флаг Decommit.

Дописываем наш трейнер на C#. Инъекции кода. Часть 1.

Один мой зритель на ютубе подметил, что трейнер наш не совсем полноценен. Исправим это недоразумение!

В этом видеоуроке вы узнаете, как можно создавать инъекции кода, используя для этого свободную память игры.

[Ссылка].


Исходный код:

static void MakeCave()
{
   if (pID != 0)
   {
      Console.WriteLine("Process Id: " + pID);
      var handle = OpenProcess(ProcessAccessFlags.All, false, pID);
      if (handle != IntPtr.Zero)
      {
         Console.WriteLine("Process handle: " + handle.ToString());
         var caveAddress = 0x004002A8;                    
         var valueon = new byte[] { 0xD9, 0x9E, 0x5C, 0x43, 0x0F, 0x00 };
         var valueoff = new byte[] { 0xD9, 0x9E, 0x5C, 0x43, 0x0F, 0x00 };
         var addressFrom = 0x00416494;
         var addressTo = addressFrom + valueoff.Length;
         var buffer = new byte[valueoff.Length];
         buffer[0] = 0xE9;                    
         var f = BitConverter.GetBytes(caveAddress-addressFrom-5);                    
         Array.Copy(f, 0, buffer, 1, f.Length);
         for (var i = 5; i < valueoff.Length; i++)
         {
            buffer[i] = 0x90;
         }
         var dummy = new UIntPtr();
         WriteProcessMemory(handle, (IntPtr)addressFrom, buffer, (uint)buffer.Length, out dummy);
         var caveBuffer = new byte[valueon.Length + 5];
         Array.Copy(valueon, caveBuffer, valueon.Length);
         caveBuffer[valueon.Length] = 0xE9;
         var retAddress = BitConverter.GetBytes(addressFrom-caveAddress-valueoff.Length);
         Array.Copy(retAddress, 0, caveBuffer, valueon.Length + 1, retAddress.Length);
         WriteProcessMemory(handle, (IntPtr)caveAddress, caveBuffer, (uint)caveBuffer.Length, out dummy); 
         CloseHandle(handle);
         Console.WriteLine("Handle closed.");
         Console.ReadKey();
      }
   }
}

ВНИМАНИЕ! В коде допущена логическая ошибка - сначала нужно записывать кейв, а только потом - прыжок на него. Ошибка допущена сознательно и исправляется простой перестановкой нескольких строчек, так что это будет домашним заданием.

20120702

Немного документации про PE-формат файлов.

PE - это у нас Portable Executable, как можно выяснить из [википедии]. Собственно, все эти exe, dll и прочие - имеют именно этот формат. Вот пара вырезок:

Адресация памяти: У Intel 386 существуют 3 модели адресации памяти - segmented, flat и real-address mode. Здесь для нас существенно знать лишь то, что программы под Windows обычно используют модель памяти flat ("плоская"). Это означает, что любое 32-х битное число может являться адресом в памяти. Таким образом программа виртуально получает в своё распоряжение 4 гигабайта адресуемой памяти. Конечно, только небольшая часть адресов соответствует реально существующей памяти. Доступ по нелегальному адресу приведёт к ошибке. Использование модели flat также означает, что программа не должна никоим образом пользоваться сегментными регистрами (селекторами). Их следует просто игнорировать. Память представляет из себя последовательность байт, числа в которой принято хранить в формате big-endian, т. е. наименее значимый байт числа сохраняется по младшему адресу (напр. 32-битное число 0x123456781 будет храниться в памяти как последовательность байт 0x78 0x56 0x34 0x12).

Вот немного про процесс загрузки файла в память:

.EXE файлы. Наконец, о том как выполняются программы под Windows. Типичная Windows-программа хранится в файле с расширением .EXE. Типичный .EXE-файл является Portable Executable (PE) - файлом. Portable Executable - это название формата файла. Помимо собственно исполнимого кода PE-файл содержит различную служебную информацию о том, как он будет загружен, таблицы импортируемых и экспортируемых функций и проч. При запуске PE-файла Windows загружает его в память почти в том виде, в котором он хранился на диске, и запускает как отдельный процесс. Каждый процесс получает в распоряжение своё собственное 32-битное адресное пространство (например, два различных процесса могут пользоваться одним и тем же адресом 0x12345, и при этом для каждого из них это будет "его собственная" память. Они не будут замечать друг друга). То, по какому адресу в этом пространстве будет загружен сам PE-файл, называется по английски Image Base и записано в одном из заголовков PE-файла. Обычно Image Base = 0x400000. Все прочие значения в служебных заголовках файла даны как смещения относительно этого адреса. Так, например Code Start (начало исполняемого кода), равное 0x1000 означает, что после загрузки файла в память, исполнение программы начнётся с адреса 0x400000 + 0x1000 = 0x401000.
DLL: Практически каждая Windows-программа пользуется функциями из динамически загружаемых библиотек (Dynamic-Link-Libraries, DLL). Важнейшими из них являются KERNEL32.DLL и USER32.DLL, которые предоставляют основные системные процедуры. Тогда как функции внутри программы вызываются просто инструкцией "CALL func" где func - адрес вызываемой функции, функцию из DLL (т. н. импортируемую функцию) таким путём вызвать нельзя, т. к. во время компиляции программы адрес её не известен. Использование DLL происходит следующим образом:
Во-первых, в заголовке PE-файла записано имя DLL, функции из которой используются в программе. При загрузке программы в память, Windows загружает в её адресное пространство и все используемые ей DLL. DLL представляет из себя такой же PE-файл, как и сама программа, но так как DLL в отличие от EXE загружается в "чужое" адресное пространство, то адрес, по которому она "хотела бы" быть загружена (её Image Base), может оказаться занят. Тогда Windows переносит её по своему усмотрению.
Во-вторых, в EXE файле записано имя каждой импортируемой функции и оставлено место, куда Windows после загрузки соответствующей DLL проставит действительный адрес функции. Это называется таблицей импортов PE-файла. Во время компиляции местонахождение таблицы импортов известно, и поэтому можно вызывать процедуры из DLL "косвенно", указывая место, где должен быть адрес вызываемой процедуры.
Думаю, для начала хватит, попозже напишу более подробную статью, но уже своими словами. Просто мы с вами так радостно ищем все эти адреса, меняем код, инъекции проводим, а как оно работает - и не знаем.

20120701

Взлом по заявкам - ломаем Весёлую Ферму (Farm Frenzy).

[Ссылка] на видеоурок. Заодно мы узнаем, что в Cheat Engine тоже есть сканер сигнатур.

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


[ENABLE]
label(timer)
registersymbol(timer)
aobscan(timer_address,D9 9E 5C 43 0F 00 E8 C1 8D FF FF 8B CE 5E 83 C4 04 E9 76 D5 FF FF)


timer_address:
timer:
nop
nop
nop
nop
nop
nop


[DISABLE]
timer:
fstp dword ptr [esi+000F435C]
unregistersymbol(timer)


Жирненьким я выделил строчку, которую нужно добавить в самом конце - чтобы созданная нами метка в памяти уничтожалась при отключении скрипта. Мелочь, но довольно существенная.

Подробности о таблице виртуальных методов.

Нарыл неплохую статейку, даже на русском. [Тык]. Даже если не очень знакомы с программированием - почитайте, много полезного можно почерпнуть.

На тему срочных вопросов.

Ребята, кто дорвался до моего скайпа или аськи - не надо приходить, писать "Привет" и пару часов ждать ответной реакции - ну не стану я на приветы отвечать, уж извините. Можно без приветов - пишите сразу по делу, ждать-то можно бесконечно. На "поболтать за жизнь" у меня правда жалко свободное время, а вот на вопросы я всегда поотвечаю.

Чуть-чуть в догонку про vTable.

В видео я рассказывал про таблицу виртуальных функций (VTable). Что это такое, по сути?

Создаётся некий объект MyObj, который умеет две функции - Init() и Destroy():

class MyObj
{
   virtual void Init() {}
   virtual void Destroy() {}
}


var myObj = new MyObj();

Как только создаётся такой объект, с ним в памяти ассоциируется таблица функций, в дизассемблированном виде выглядящая как:

...
jmp init
...
...
jmp destroy
...

Если смотреть на это с точки зрения Direct3D, то все реализации (код) функций лежат в dll с именем d3d.dll, а когда создаётся новый объект d3d - получается вот такая картина:

Игровой код:
call [d3d+0x123]


vTable:
0x123: jmp init


d3d.dll:
0x456: init

Т.е. созданный объект стучится в присвоенную ему vTable, находит там (по смещению от начала) нужную функцию, а уже она указывает на конкретную реализацию в библиотеке.

Помните смещение из видео? [ECX+0AC]? 0xAC = 172 в десятичной системе счисления. 172/4 = 43. Почему делим на 4? Потому что все указатели (а vTable - это таблица указателей) занимают ровно 4 байта. Отнимаем ещё 4 байта (первые 4 - указатель на саму таблицу) - получаем, что Clear() находится на 42-й позиции в vTable. И это всегда так (для данной версии DirectX). Приятность тут в том, что это не зависит от игры. Никак. Если 20 игр используют эту версию библиотеки - во всех играх эта функция будет находиться по этому смещению в vTable.

Нам остаётся только как-то найти всё это дело, не вмешиваясь особо в код игры.

Рассуждаем о D3D вслух.

Попутно ковыряясь в нашем D3D-приложении при помощи острого, аки скальпель, OllyDbg.

[Ссылка].
Раз немного передохнули, подытожим. Вспомним нашу программку на ассемблере, рисующую прямоугольник. Там это выглядело примерно так:

.wmpaint:
invoke BeginPaint,[hwnddlg],ps
mov [hDC],eax
invoke Rectangle,[hDC],10,10,30,30
invoke EndPaint,[hwnddlg],ps
jmp .processed

Значит, хочет наше окно себя нарисовать. В очередь сообщений прилетает WM_PAINT. Согласно [документации]:

The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window.

Типа, раз такой праздник произошёл, значит окно хочет себя перерисовать. Перво-наперво ему же надо узнать, где рисовать, да? Вызываем [BeginPaint], сообщаем ему хэндл нашего окна и структуру, для чего-то там необходимую. Нам, в общем-то, до лампочки, для чего именно - она просто есть и всё.
В ответ на бегинпейнт (НачатьРисовать, лол) нам приходит хитрая штука под названием Handle  to Device Context, если чуть более по-русски - это хэндл связки видеокарты с монитором. Типа, то место, где рисовать можно, ну то есть холст, потому как видеокарт и мониторов всяких - тьма тьмущая, вот и сделали такое клёвое обощение.

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

После всего этого безобразия нам надо сообщить, что безобразие закончилось - вызываем EndPaint, ЗакончитьРисовать.
И на выход.

Вот, собственно, с Direct3D всё примерно точно так же обстоит - у нас есть указатель на устройство, очень похожий на hDC, т.е. то, где рисовать. Мы говорим:

BeginScene();

Мол, давай рисовать!

Рисуем...

И затем говорим:

EndScene();

Хватит это терпеть! Довольно!
Вот и во всех играх та же самая фигня происходит. Они делают себе место для рисования, каждый кадр говорят BeginScene(), рисуют всё, что им надо, а затем говорят EndScene() и показывают всё это на экране. Разработчикам не надо париться ни о модели видеокарты, ни о мониторе - просто РИСАТЬ и всё. Удобно!

Тут прискакиваем мы, все в белом и на соответствующем коне, толпы пищащих поклонниц сопровождают нас и кидаются в нас нижним бельём. Нам вдруг ВЗДУМАЛОСЬ игру обмануть и нарисовать в её королевстве что-то ещё. В чём логика:

BeginScene();
РисуемВсёИгровое();
EndScene();


Нам надо взять и нарисовать что-то своё после всего игрового. Удобнее всего это сделать прямо перед вызовом EndScene(), типа вот так:

BeginScene();
РисуемВсёИгровое();
РисуемЧужое();
EndScene();

Хитрые человеки уже давно смекнули, что можно же сделать инъекцию и подменить собой вызов EndScene:

BeginScene();
РисуемВсёИгровое();
CustomEndScene();

void CustomEndScene() {
DrawRectangle();
DrawText();
EndScene();

Получается, что игра хочет вызвать EndScene, вместо этого попадает в нашу CustomEndScene, где мы сначала дорисовываем что-то своё, а потом уже вызываем оригинальную функцию EndScene. В итоге игра справедливо считает, что так и было задумано. Магия!

Вот таким вот нехитрым образом и работает большинство хаков, связанных с графикой - начиная с wallhack и maphack, заканчивая экранными меню.

Сложность состоит в том, чтобы найти EndScene() и указатель на устройство, который где-то до этого получает игра, потому что только лишь зная указатель на устройство мы можем что либо рисовать. Собственно, скоро мы вооружимся OllyDbg и пойдём наш пример курочить. To be continued!
Чтобы оставаться в тонусе, когда голова пухнет от всех этих DirectX, вот вам кошка в дыньке:

Ну что, как вам код? Приходит понимание потихоньку? В общем-то, ничего сложного. В главной функции программы мы делаем исключительно две вещи:

1. Создаём и показываем на экране окно, получив его хэндл.
2. Инициализируем Direct3D.

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

В перерывах же (между обработкой всех поступающих сообщений) вызывается функция render_frame(), которая рисует один кадр, воспользовавшись уже инициализированным Direct3D.

С инициализацией всё чуть сложнее, но не более. Вот код функции:

void initD3D(HWND hWnd) {
   d3d = Direct3DCreate9(D3D_SDK_VERSION);
   D3DPRESENT_PARAMETERS d3dpp;
   ZeroMemory(&d3dpp, sizeof(d3dpp));
   d3dpp.Windowed = TRUE;
   d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
   d3dpp.hDeviceWindow = hWnd;

   d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
}

И для её работы у нас есть две загадочных переменных:

LPDIRECT3D9 d3d; //Указатель на Direct3D-интерфейс
LPDIRECT3DDEVICE9 d3ddev; //Указатель на устройство


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

Представим, что монитор у нас двухцветный - чёрно-белый. И вот что на нём нарисовано:


Буква "А". Монитор у нас, кстати, аж 20 на 20 пикселей.
Напомню, что система координат у нас довольно хитрая - две оси, горизонтальная и вертикальная, а точка отсчёта - верхний-левый угол экрана:


Белые пиксели у нас имеют значение 0, чёрные - 1. Что ж нам нужно сделать, чтобы получить букву "А" на экране?

Компьютер не понимает, что у нас есть две оси - он сидит только длинную простыню:

000000000000000...

И так далее. Раз у нас монитор размерами 20х20 пикселей, то получается, что у нас есть 20 строчек по 20 пикселей. 0 - белых, 1 - чёрных. Вот и выходит:

111000000000000000000000000000000000
101000000000000000000000000000000000
111000000000000000000000000000000000
101000000000000000000000000000000000
000000000000000000000000000000000000
000000000000000000000000000000000000

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

Похоже? По-моему, вполне.

Дык вот. Что есть Direct3D? Direct3D - это кусок DirectX, отвечающий за рисование. По сути это - набор функций, берущих на себя всю ответственность за командование видеокартой и монитором. Не будем вдаваться в лишние сейчас подробности. Просто командуем, d3d - нарисуй мне прямоугольник! Вот тебе видеокарта! Рисуй, я сказал! - и оно рисует.

Собственно, чтобы командовать этим набором функций - нам надо его как-то иметь при себе. Этим занимается офигенска команда [Direct3DCreate9] - создаёт новый объект Direct3D, возвращая указатель на него. Добавляем первый комментарий:

void initD3D(HWND hWnd) {
   d3d = Direct3DCreate9(D3D_SDK_VERSION); //Создаём и получаем указатель на объект d3d
   D3DPRESENT_PARAMETERS d3dpp;
   ZeroMemory(&d3dpp, sizeof(d3dpp));
   d3dpp.Windowed = TRUE;
   d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
   d3dpp.hDeviceWindow = hWnd;
   d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
}


Так, клёво. Объект, к которому можно обратиться и приказать ему рисовать, мы получили. Осталось выяснить, где рисовать - то есть достучаться до нашей видеокарты. Двумя строчками ниже мы объявляем специальную структуру, нужную для этого самого "достучаться". Заодно заполняем некоторые параметры:

void initD3D(HWND hWnd) {
   d3d = Direct3DCreate9(D3D_SDK_VERSION); //Создаём и получаем указатель на объект d3d
   D3DPRESENT_PARAMETERS d3dpp; //Структура, описывающая устройство   ZeroMemory(&d3dpp, sizeof(d3dpp)); //Очищаем её
   d3dpp.Windowed = TRUE; //Говорим, что мы в окне
   d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; //Скучно и не интересно
   d3dpp.hDeviceWindow = hWnd; //Скармливаем хэндл окна

   d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); //Получаем устройство!
}

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

Если в голове укладывается плохо, то просто запомните, что объект D3D - это палитра и кисть, а устройство D3D - это холст. Чтобы нарисовать на холсте, надо провести по нему кистью - делов-то!


Дело остаётся за малым, а именно - за рисованием. Как мы помним, у нас крутится цикл, который вызывает render_frame(). Вот её код:

void render_frame(void) {
   d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);

   d3ddev->BeginScene();
   d3ddev->EndScene();
   d3ddev->Present(NULL, NULL, NULL, NULL);
}


ЩИТО оно делает? А всё просто. Мы обращаемся к нашему устройству и говорим ему [очистить] всю область рисования нежным синим цветом. После этого мы вызываем две функции, которые подразумевают под собой начало и конец рисования (помните BeginPaint и EndPaint и предыдущих статей?), ничего не рисуем и говорим показать результат а экране. И он показывает!

Всё, созерцайте, а то я и так уже разошёлся.

#include //Подключаем необходимые для работы заголовочные файлы
#include
#include


#pragma comment (lib, "d3d9.lib") //И библиотеки


//Аж две глобальных переменных!
LPDIRECT3D9 d3d;    //Указатель на интерфейс Direct3D
LPDIRECT3DDEVICE9 d3ddev;    //Указатель на устройство


//Прототипы функций
void initD3D(HWND hWnd);    //Вот эта - инициализирует Direct3D
void render_frame(void);    //Эта - рисует кадр
void cleanD3D(void);        //А вот эта - завершает работу Direct3D и чистит за ней весь мусор


//Прототип функции главного окна программы
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


//Точка входа - отсюда начинается выполнение всего и вся
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    HWND hWnd; //Переменная для хэндла главного окна
    WNDCLASSEX wc;
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX); //Вот отсюда и ниже - инициализируем окно, ничего интересного
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = L"WindowClass";
    RegisterClassEx(&wc); //Регистрируем новое окно в системе
    //Создаём его, получаем его хэндл
    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our First Direct3D Program", WS_OVERLAPPEDWINDOW, 300, 300, 800, 600, NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow); //Показываем его (окно)    
    initD3D(hWnd); //Инициализируем Direct3D    
    MSG msg;
    while(TRUE) { //Создаём и инициалириуем очередь сообщений для нашего окна
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { //Которая будет крутиться в цикле
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if(msg.message == WM_QUIT) break; //Пока мы не попытаемся окно закрыть
        render_frame(); //А пока крутится - рисуем!
    }    
    cleanD3D(); //Как закрыли окно - отключаем всю графику
    return msg.wParam; //И выходим
}


//Бесполезная и никчёмная процедура обработки нашего окна
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch(message) {
        case WM_DESTROY: { //Которая умеет только лишь выходить
                PostQuitMessage(0);
                return 0;
            } break;
    }
    return DefWindowProc (hWnd, message, wParam, lParam);
}


//Та самая функция инициализации Direct3D
void initD3D(HWND hWnd) {
    d3d = Direct3DCreate9(D3D_SDK_VERSION); //Тут комментов нет, поэтому я чуть ниже остановлюсь на этом поподробней
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;    
    d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev);
}


//Функция, рисующая кадр
void render_frame(void) {    
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); //Берём и очищаем всё окно синим цветом!
    d3ddev->BeginScene(); //При желании - начинаем рисовать что-то    
    d3ddev->EndScene(); //И заканчиваем
    d3ddev->Present(NULL, NULL, NULL, NULL); //Показываем всё это безобразие на экран
}


//Функция, завершающая работу с графикой
void cleanD3D(void) {
    d3ddev->Release(); //Говорим устройству, давай до свидания!
    d3d->Release(); //Говорим то же самое интерфейсу Direct3D
}