В данной статье речь пойдет о перехвате сообщений в чат, которые посылаются другими плагинами AMXX. Заметил, что многие об этом спрашивали на форуме, как это сделать. Собственно, перехватывать будем функцию client_print. Зачем это нужно? К примеру, у вас нет исходника плагина, а вам нужно или изменить сообщение, или русифицировать его, а может даже убрать вшитую в плагин рекламу. Для перехвата нам понадобится модуль Вы должны зарегистрироваться, чтобы видеть ссылки..
Итак, начнем с простого. Функция для вывода сообщений имеет следующий синтаксис:
[pawn]client_print(index, type, const message[], ...) [/pawn]
index - id игрока или 0, если нужно отправить сообщение всем игрокам
type - тип сообщения:
- print_chat - сообщение в чат
- print_console - сообщение в консоль
- print_notify - сообщение в консоль при режиме разработчика (developer)
- print_center - сообщение по центру
Теперь попробуем обратиться к исходникам мода AMX Mod X. В файле amxmodx.cpp находим нашу функцию:
[pawn]
static cell AMX_NATIVE_CALL client_print(AMX *amx, cell *params) /* 3 param */
{
int len = 0;
char *msg;
if (params[1] == 0)
{
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CPlayer *pPlayer = GET_PLAYER_POINTER_I(i);
if (pPlayer->ingame)
{
g_langMngr.SetDefLang(i);
msg = format_amxstring(amx, params, 3, len);
msg[len++] = '\n';
msg[len] = 0;
UTIL_ClientPrint(pPlayer->pEdict, params[2], msg);
}
}
} else {
int index = params[1];
if (index < 1 || index > gpGlobals->maxClients)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid player id %d", index);
return 0;
}
CPlayer* pPlayer = GET_PLAYER_POINTER_I(index);
g_langMngr.SetDefLang(index);
msg = format_amxstring(amx, params, 3, len);
msg[len++] = '\n';
msg[len] = 0;
if (pPlayer->ingame)
UTIL_ClientPrint(pPlayer->pEdict, params[2], msg); //format_amxstring(amx, params, 3, len));
}
return len;
}
[/pawn]
Внимательно изучив код, можно заметить, что в нем вызывается функция UTIL_ClientPrint. Открываем util.cpp и находим ее:
[pawn]void UTIL_ClientPrint(edict_t *pEntity, int msg_dest, char *msg)
{
if (!gmsgTextMsg)
return; // :TODO: Maybe output a warning log?
char c = msg[190];
msg[190] = 0; // truncate without checking with strlen()
if (pEntity)
MESSAGE_BEGIN(MSG_ONE, gmsgTextMsg, NULL, pEntity);
else
MESSAGE_BEGIN(MSG_BROADCAST, gmsgTextMsg);
WRITE_BYTE(msg_dest);
WRITE_STRING(msg);
MESSAGE_END();
msg[190] = c;
} [/pawn]
Данную функцию (UTIL_ClientPrint) мы и будем перехватывать. Теперь нам нужно найти сигнатуру этой функции для Windows и Linux версий библиотеки. А затем создать конфиг к Orpheu и использовать ее для перехвата сообщений. Сигнатуры находим с помощью программы IDA Pro.
[align=center]
[/align]
Так как мы будем искать функцию в библиотеке AMXX, то нужно добавить в Orpheu его поддержку. Для этого в директории ..\addons\amxmodx\configs\orpheu\libraries создадим файл amxmodx. Добавим в него:
- Код: Выделить всё
[
"amxmodx", "amx_mode"
]
Теперь создадим конфигурационный файл для Orpheu в директории ..\addons\amxmodx\configs\orpheu\functions с именем UTIL_ClientPrint. Содержание файла должно быть таким:
- Код: Выделить всё
{
"name" : "UTIL_ClientPrint",
"library" : "amxmodx",
"arguments" :
[
{
"type" : "pointer"
},
{
"type" : "int"
},
{
"type" : "char *"
}
],
"identifiers":
[
{
"os" : "windows",
"value" : [0x83,"*","*","*","*","*","*",0x74,"*",0x8B,"*","*","*",0x85,0xC0,0x53,0x56,0x8B,"*","*","*",0x8A,"*","*","*","*","*",0xC6,"*","*","*","*","*","*",0x74,"*",0x50,0xA1,"*","*","*","*",0x6A,0x00,0x50,0x6A,0x01,0xEB,"*"]
},
{
"os" : "linux",
"value" : "_Z16UTIL_ClientPrintP7edict_siPc"
}
]
}
name - имя нашей функции
library - библиотека, где нужно искать функцию, ставим amxmodx, так как мы добавили поддержку
arguments - аргументы функции UTIL_ClientPrint, если вернуться к исходникам AMXX, то функция имеет данный вид:
[pawn]UTIL_ClientPrint(edict_t *pEntity, int msg_dest, char *msg) [/pawn]
Так как Orpheu не поддерживает тип edict_t, вместо этого указываем pointer. Второй тип - int, третий - char *.
identifiers - идентификаторы функции, в данном случае это сигнатуры
os - операционная система (windows/linux)
value - значение, в данном случае сигнатура (на скриншотах выше выделено красным цветом)
Все, наш конфиг готов. Еще один важный момент, как видно функция использует edict_t *pEntity, что нам не подходит, потому что это указатель на объект, а не его id. А для того, чтобы правильно перехватить id игрока, которому отправляется сообщение, нам нужно будет использовать еще одну функцию из engine модуля игры под названием IndexOfEdict.
[align=center]
[/align]
Теперь создадим конфигурационный файл для Orpheu в директории ..\addons\amxmodx\configs\orpheu\functions с именем IndexOfEdict. Содержание файла должно быть таким:
- Код: Выделить всё
{
"name" : "IndexOfEdict",
"library" : "engine",
"return" :
{
"type" : "int"
},
"arguments" :
[
{
"type" : "pointer"
}
],
"identifiers":
[
{
"os" : "windows",
"value" : [0x55,0x8B,0xEC,0x8B,"*","*",0x85,0xC0,0x75,"*",0x5D,0xC3,0x8B,"*","*","*","*","*",0x56,0x2B,0xC1,0x8B,0xC8,0xB8,"*","*","*","*"]
},
{
"os" : "linux",
"value" : "IndexOfEdict"
}
]
}
return - тип возвращаемого значения
Осталось применить полученные знания и конфиги на практике, рассмотрим примерный плагин:
[pawn]#include <amxmodx>
#include <orpheu>
#define PLUGIN "Hook client_print"
#define VERSION "1.0"
#define AUTHOR "DJ_WEST"
// Используем для проверки, что объект - это игрок, если входит значение в промежуток между 1 и 32
#define IsPlayer(%1) (1 <= (%1) <= 32)
// Создаем переменную для хранения указателя на функцию IndexOfEdict
new OrpheuFunction:o_IndexOfEdict
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
// Регистрируем перехват на функцию UTIL_ClientPrint
// On_ClientPrint_Pre - функция, которая будет вызываться при вызове функции UTIL_ClientPrint
// OrpheuHookPre - означает, что перехватываем функцию перед ее вызовом
OrpheuRegisterHook(OrpheuGetFunction("UTIL_ClientPrint"), "On_ClientPrint_Pre", OrpheuHookPre)
// Помещаем в переменную o_IndexOfEdict указатель на функцию IndexOfEdict
o_IndexOfEdict = OrpheuGetFunction("IndexOfEdict")
}
// Указываем аргументы для функции относительно тех, что прописаны в конфиге
// p_Edict - указатель на объект
// i_Type - тип сообщения
// s_Message - текст сообщения
public OrpheuHookReturn:On_ClientPrint_Pre(p_Edict, i_Type, s_Message[190])
{
// Удаляем пробелы с начала и конца s_Message
trim(s_Message)
// Если полученное сообщение s_Message равняется указанному
// и тип сообщения равен print_chat (вывод в чат)
if (equal(s_Message, "Type 'amx_help' in the console to see available commands") && i_Type == print_chat)
{
// Создаем переменную для хранения id игрока
static id
// Вызываем функцию IndexOfEdict, передавая ей указатель p_Edict
// Полученное значение сохраняем в переменную, как id игрока
id = OrpheuCall(o_IndexOfEdict, p_Edict)
if (IsPlayer(id))
{
// Если мы хотим заменить сообщение своим, то посылаем новое
// Если хотим только блокировать, то не посылаем
client_print(id, i_Type, "Visit http://amx-x.ru", id)
// Используем OrpheuSupercede для блокировки выполнения функции UTIL_ClientPrint
// Следовательно оригинальное сообщение, которое мы заменяем, игрок не увидит
return OrpheuSupercede
}
}
// Если сообщение не подходит нашему условию
// то просто продолжаем выполнение функции UTIL_ClientPrint
return OrpheuIgnored
} [/pawn]