Оглавление:
- Часть 1: основы
- Часть 2: практика - пишем античит на HP
Внимание! Эта тема закрыта от комментариев. Если у вас есть вопросы по поводу перехватов - внимательно прочтите вторую часть урока, и если что-то останется для вас непонятным, задавайте вопросы в той теме.
Всем привет.
Сегодня я расскажу вам о перехвате функций в Pawn.
Для начала разберёмся, что такое перехваты и с чем их едят.
Перехваты в SA:MP обычно используется для того, чтобы изменить поведение каких-либо функций.
Классическим примером может послужить написание античита на HP.
Предположим, у нас есть глобальный массив, в котором мы будем записывать HP игроков после его выдачи с помощью SetPlayerHealth.
Без перехватов придётся выискивать в моде все вызовы SetPlayerHealth и под каждым из них вручную записывать выдаваемое количество HP в ячейку массива.
В то же время, используя технику перехвата, достаточно всего лишь написать 1 перехватчик, в котором происходит та же запись в массив, и поместить его в самое начало мода, перед подключением a_samp.inc. В дальнейшем перехваты вместе с остальными переменными и функциями античита можно вынести в отдельный инклуд (например, "ac_hp.inc"), который при необходимости можно будет отключить, всего лишь закомментировав строку #include "ac_hp.inc".
Таким образом, техника перехватов даёт возможность "прицепить" свой код к стандартным функциям и коллбэкам без вмешательства в сам мод, в разы упростив дальнейшую разработку.
В этом уроке будет описан только один метод, который был открыт благодаря ipsBruno и Y_Less.
Вся суть данного метода состоит в том, что макропроцессор обрабатывает выражения "#if defined X" в 2 прохода, поэтому если "X" - название ещё не известной компилятору функции/переменной/константы, во время 1-го прохода макропроцессор пропустит такой макрос и сначала соберёт информацию о функциях, переменных и константах в скрипте, завершив 1-й проход.
После этого на 2-м проходе компилятор уже будет знать обо всех переменных, функциях и константах, расположенных не только перед строкой с "#if defined", но и после неё. В результате перехватчик подменит собой перехватываемую функцию, а затем вызовет её, если она существует. Если же перехватываемой функции не существует, будет вызван только перехватчик.
Преимущества такого способа перед другими методами:
- Не требуется вызывать CallLocalFunction, благодаря чему не происходит увеличения нагрузки из-за использования нативных функций.
- Не используется модификация кода во время его выполнения (из-за таких модификаций могла возникнуть несовместимость кода с JIT).
Рассматриваемый нами метод перехвата работает примерно так:
В результате при умелом использовании одна или несколько функций-перехватчиков могут быть выстроены в цепочку: сначала вызывается первый объявленный перехватчик, а за ним и все остальные, пока очередь не дойдёт до оригинальной перехватываемой функции:Код:<перехватчик> <код перехвата> <вызов перехватываемой функции> <конец перехватчика> <функция уже была перехвачена?> <удалить старый перехват> <иначе> <дать знать, что теперь функция перехвачена> <конец ветвления> <вставить перехватчик в цепочку вызовов> <предварительно объявить функцию>
В Pawn существуют 2 способа реализации этого метода: один для перехвата коллбэков (функций, автоматически вызываемых сервером; пример: OnPlayerConnect) и один для нативных функций (функций, реализованных в самом сервере; пример: printf, SendClientMessage).Код:<перехватчик1> <выполнить набор действий перехватчика> <вызвать перехватчик2> <конец перехватчика> <перехватчик2> <выполнить набор действий перехватчика> <вызвать перехватчик3> <конец перехватчика> <...> <перехватчикN> <выполнить набор действий перехватчика> <вызвать перехватываемую функцию> <конец перехватчика> <перехватываемая функция> <...> <конец функции>
Перехват коллбэков реализуется следующим образом:
Перехват нативных функций производится похожим образом, но, в отличие от первого способа, перехватываемая функция не переименовывается, поэтому не требуется делать для неё новое предварительное объявление (forward).
Теперь разберёмся, как всё это выглядит в деле.
Методы для коллбэков и нативных функций разные, поэтому рассмотрены они будут отдельно.
- Перехват коллбэков на примере OnDialogResponse:
// массив, в котором записаны ID диалогов ,показанных другим игрокам static player_dialog_ids[MAX_PLAYERS] = {-1, ...}; // перехватчик { // если игрок посылает сигнал об ответе на диалог, // который ему не показывали - это признак использования читов // (и да, я знаю, что сейчас этой проблемы в SA:MP нет, это всего лишь пример!) if(player_dialog_ids[playerid] != dialogid) Kiсk(playerid); player_dialog_ids[playerid] = -1; #if defined spd__OnDialogResponse return spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext); #endif } #if defined _ALS_OnDialogResponse #undef OnDialogResponse #else #define _ALS_OnDialogResponse #endif #define OnDialogResponse spd__OnDialogResponse #if defined spd__OnDialogResponse forward spd__OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]); #endif- Перехват нативной функции на примере ShowPlayerDialog:
static player_dialog_ids[MAX_PLAYERS] = {-1, ...}; // перехватчик stock spd__ShowPlayerDialog(playerid, dialogid, type, header[], text[], button1[], button2[]) { player_dialog_ids[playerid] = dialogid; } #if defined _ALS_ShowPlayerDialog #undef ShowPlayerDialog #else #define _ALS_ShowPlayerDialog #endif #define ShowPlayerDialog spd__ShowPlayerDialog
На сегодня всё, удачного скриптинга!
Для тех, кто любит стрелять себе в ногу:
P.S.: Скоро будет готова 2-я часть урока, в которой будет рассматриваться использование перехватов на практике.
Часть 2.
Автор: Daniel_Cortez
Специально для Pro-Pawn.ruКопирование данной статьи на других ресурсах без разрешения автора запрещено!