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() (смещения мы знаем, в отличие от указателя) - и вуаля! Можно заменять. Ненужный уже после этого указатель мы уничтожаем.

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

1 комментарий:

  1. Этот комментарий был удален администратором блога.

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

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