В общем, ребята, как вы могли заметить - мои знания постепенно подходят к концу, так что графику я изучаю, можно сказать, вместе с вами. Что мы уже знаем?
Есть некоторый интерфейс (назовём его набором функций) для отрисовки графики. Командуешь ему команды - он выполняет. Если представить абстрактно, то выглядит это примерно так:
На картинке буковка "А" - это наша программа, "В" - это видеокарта+монитор, а 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() (смещения мы знаем, в отличие от указателя) - и вуаля! Можно заменять. Ненужный уже после этого указатель мы уничтожаем.
Собственно, я пока продолжу экспериментировать, а вы комментируйте, если что-то не совсем ясно. Старался объяснять максимально понятно, но я и сам не до конца ещё всё это понимаю, а сделать уже очень хочется. :)
Есть некоторый интерфейс (назовём его набором функций) для отрисовки графики. Командуешь ему команды - он выполняет. Если представить абстрактно, то выглядит это примерно так:
Одна проблема - на всех одной 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() (смещения мы знаем, в отличие от указателя) - и вуаля! Можно заменять. Ненужный уже после этого указатель мы уничтожаем.
Собственно, я пока продолжу экспериментировать, а вы комментируйте, если что-то не совсем ясно. Старался объяснять максимально понятно, но я и сам не до конца ещё всё это понимаю, а сделать уже очень хочется. :)
Этот комментарий был удален администратором блога.
ОтветитьУдалить