ВНИМАНИЕ! Предполагается, что читатель знаком с C++ и уже имеет опыт написания программ.
I. Предисловие
- В этой статье будет рассмотрено использование и обработка оригинальных функций, доступных из hlsdk. Плагин из первой главы, в действительности, взаимодействовал только с метамодом. Пришло время "вдохнуть жизнь" в наш первый плагин. А именно, начать работать с доступными оригинальными функциями движка.
- На самом деле, meta-плагин ничем не отличается от обычной dynamic load library(.dll или .so). Точнее, это и есть динамическая библотека, только метод загрузки библиотеки у метамода свой. Поэтому вы можете в полной мере использовать все возможности C++. Тут хочу подчеркнуть, что структура исходных файлов программы может быть любой: от одного файла, до десятков\сотен.
II. Таблицы функций
Структуры, содержащие указатели на определенные функции называются таблицами функций. Существует множество разных таблиц, которые могут использоваться в ваших плагинах(даже ваши собственные), но мы остановимся на главном - таблицы оригинальных функций. Тут хочу подчеркнуть, что таблицы содержат указатели, или адреса функций, которые могут быть совершенно любыми - полученные от движка или определенные в вашем плагине(имеется в виду любые функции одного типа - возвращемое значение как и аргументы должны совпадать).
Таблицы оригинальных функций можно разделить на три группы:
- engine - Вы должны зарегистрироваться, чтобы видеть ссылки.
- dllapi - Вы должны зарегистрироваться, чтобы видеть ссылки.
- newdll - Вы должны зарегистрироваться, чтобы видеть ссылки.
Также существует еще одна немаловажная таблица, о которой пойдет речь в пункте 4.
Таблица engine-функций уже использовалась в предыдущей главе. Функция GiveFnptrsToDll(т.е. сам движок) передает таблицу с адресами engine-функций. Это было необходимо, т.к. в одном из заголовочных файлов metasdk включен файл из hlsdk - enginecallback.h, который содержит объявление таблицы функций, которая определена уже в нашем плагине - g_engfuncs, а также содержит макросы для вызова оригинальных функций. Один из них был использован в нашем плагине - ALERT - функция, с помощью которой выводится сообщение в консоль сервера.
III. Вызов оригинальных функций
С вызовами функций все довольно просто:
- Для вызова engine-функций предназначены макросы из файла enginecallback.h(также указаны на сайте метамода в списках функций, обычно ЗАГЛАВНЫМИ буквами)
- Для вызова dllapi-функций предназначены команды типа MDLL_*(аргументы) - они были указаны в первой главе
- Для вызова newdll-функций предназначены команды типа MNEW_*(аргументы) - они были указаны в первой главе
IV. Обработка вызовов оригинальных функций
Управление обработкой функций во главе осуществляет метамод, а значит ему мы должны передать таблицу, содержащую функции-трансферы, т.е. те функции, которые будут передавать таблицы функций-обработчиков(содержащие функции, которые будут вызываться перед или после вызова оригинальной функции в вашем плагине) непосредственно движку или метамоду. Эта структура носит название META_FUNCTIONS, которая состоит из 8 элементов. Если какая-либо из таблиц не используется, то на место функции-трансфера вы должны установить значение NULL.
Для каждой группы функций существует по две функции-трансфера, отвечающие за передачу таблиц функций-обработчиков в метамод:
- int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion );
int GetEntityAPI_Post( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion );
int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion );
int GetEntityAPI2_Post( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); - int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pNewFunctionTable, int *interfaceVersion );
int GetNewDLLFunctions_Post( NEW_DLL_FUNCTIONS *pNewFunctionTable, int *interfaceVersion ); - int GetEngineFunctions( enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion );
int GetEngineFunctions_Post( enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion );
Вы заметили, что существует два варинта передачи dllapi-функций - GetAPI и GetAPI2? Вот что я нашел по этому поводу:
Не совсем понятна разница между GetAPI и GetAPI2; они обе существуют для передачи одной и той же таблицы.
И все-таки только одна из них предназначена для использования. Если в DLL присутствует GetAPI2, движок вызовет её, но не будет вызывать GetAPI. Если движок не может обнаружить GetAPI2 в DLL, то он будет использовать GetAPI.
Получается, GetAPI2 создан для замены GetAPI. Он был добавлен в SDK версии 2.0. Я считаю, что с появлением нового SDK появилась необходимость проверять версию интерфейса, и движок не может определить, что DLL устарела через GetAPI, т.к. там не используется указатель для передачи версии интерфейса.
Остается непонятным одно - требуется ли вызов GetAPI в DLL, в которой используется SDK 2.0+ или нет...
В оригинале:
It's not clear what the difference is between GetAPI and GetAPI2; they
both appear to return the exact same function table.
Only one of them appears to be ever called, though. If the DLL provides
GetAPI2, the engine/hlds will call that, and will not call GetAPI. If
the engine couldn't find GetAPI2 in the DLL, it appears to fall back to
GetAPI.
So, GetAPI2 appears to replace GetAPI, and appears to have been added
with SDK 2.0. My best guess is that, with the new SDK, interface
version checking became important, and without the int ptr used in
GetAPI2, the engine can't find out the version of the DLL via GetAPI.
It's unclear whether a DLL coded under SDK2 needs to provide the older
GetAPI or not..
С помощью этих функций-трансферов передаются таблицы, содержащие функции-обработчики из вашего плагина. Если обработка этой для этой функции отсутствует - необходимо установить значени NULL на место этой функции. Таблица META_FUNCTIONS передается при вызове meta_attach(смотрите аргументы функции meta_attach).
Схема передачи таблиц функций в виде иерархии:
- Через meta_attach передается таблица META_FUNCTIONS
- META_FUNCTIONS содержит функциии-трансферы(GetAPI2, GetEngine итд.)
- Функции-трансферы передают таблицы enginefuncs_t, DLL_FUNCTIONS, NEW_DLL_FUNCTIONS
- Таблицы enginefuncs_t, DLL_FUNCTIONS, NEW_DLL_FUNCTIONS содержат функции-обработчики
- Функции-обработчики из вашего плагина вызываются перед или после нужными оригинальными функциями
- Таблицы enginefuncs_t, DLL_FUNCTIONS, NEW_DLL_FUNCTIONS содержат функции-обработчики
- Функции-трансферы передают таблицы enginefuncs_t, DLL_FUNCTIONS, NEW_DLL_FUNCTIONS
- META_FUNCTIONS содержит функциии-трансферы(GetAPI2, GetEngine итд.)
Все станет ясно после небольшого примера.
V. Как это работает
Разберем на примере использование таблиц функций. Попутно будет затронут еще ряд вопросов. За основу будет взят плагин из первой главы.
Обратимся к основной функции - GiveFnptrsToDll. Как уже говорилось - эта функция принимает от движка таблицу с адресами оригинальных функций, которая используется для вызова этих функций из движка. Забирать указанную таблицу функций - обязательно, этого требует SDK, и вам она тоже обязательно пригодится.
Если вы взглянете на аргументы функции, то заметите одну интересную деталь - вместе с таблицой функций передается структура globalvars_t. Само название говорит о том, что она содержит глобальные переменные сервера. Её описание находится в файле progdefs.h. Я не зря заострил внимание на этом моменте - данная структура представляет собой полезный инструмент, например, прямой доступ к названию карты сервера. Пусть её изучение останется вашим домашним заданием.
В данном случае будет рассмотрена обработка только одной таблицы - необходимые сведения есть в пункте ниже - нету смысла писать одно и то же.
Для начала вы должны решить - какие функции вам понадобятся и создать для них функции-обработчики(эквивалентно client_connect, fw_PlayerKilled итд. из amxx-плагинов). После того, как вы определились и создали свои функции-обработчики, необходимо сформировать таблицу функций типа enginefuncs_t и заполнить её, установив на место необходимых функций ваши функции-обработчики, а на места остальных - значение NULL. Расположение функций в таблице можно посмотреть в описании структуры, либо в листинге ниже.
Как только таблица функций готова, необходимо сформировать функцию передачи этой таблицы метамоду/движку. Здесь есть пара нюансов. Перед тем, как отправить таблицу с вашими функциями необходимо сделать ряд проверок:
- Первая и немаловажная проверка - правильность адреса конечной таблицы, передаваемой через аргументы функции. Эта проверка важна тем, что при попытке получить доступ к нулевой памяти вы получите segfault или access violation. Поэтому лучше подобную ситуацию предотвратить.
- Вторая проверка - сравнивание версий интерфейсов. Она также обязательна, потому что при не совпадении структур и при попытке обратиться по несуществующему адресу также обернется для вас ошибкой доступа к памяти, либо логической ошибкой, что еще хуже.
Есть еще одна важная структура - глобальные переменные метамода - структура meta_globals_t. Данная структура обязательна в использовании, т.к. содержит в себе текущее значение META_RESULT функции(взгляните макро RETURN_META или листинг структуры и все поймете). Пусть её изучение также останется на дом.
В итоге мы получаем это:
- Код: Выделить всё
#include <extdll.h>
#include <meta_api.h>
meta_globals_t *gpMetaGlobals;
plugin_info_t info = {
META_INTERFACE_VERSION, // ifvers
"HELLO WORLD", // name
"1.01", // version
"2011/02/19", // date
"6a6kin", // author
"http://ultra.ucoz.ru", // url
"HELLOWORLD", // logtag, all caps please
PT_ANYTIME, // (when) loadable
PT_ANYPAUSE // (when) unloadable
};
static META_FUNCTIONS gMetaFunctionTable =
{
NULL, // pfnGetEntityAPI HL SDK; called before game DLL
NULL, // pfnGetEntityAPI_Post META; called after game DLL
NULL, // pfnGetEntityAPI2 HL SDK2; called before game DLL
NULL, // pfnGetEntityAPI2_Post META; called after game DLL
NULL, // pfnGetNewDLLFunctions HL SDK2; called before game DLL
NULL, // pfnGetNewDLLFunctions_Post META; called after game DLL
GetEngineFunctions, // pfnGetEngineFunctions META; called before HL engine
NULL // pfnGetEngineFunctions_Post META; called after HL engine
};
enginefuncs_t g_engfuncs;
globalvars_t *gpGlobals;
#if defined _MSC_VER
#pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8")
#endif
C_DLLEXPORT void WINAPI GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals)
{
memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t));
gpGlobals = pGlobals;
ALERT(at_console, "[HELLOWORLD]: GiveFnptrsToDll\n");
}
C_DLLEXPORT int Meta_Query(char *interfaceVersion, plugin_info_t **pinfo, mutil_funcs_t *pMetaUtilFuncs)
{
*pinfo = &info;
ALERT(at_console, "[HELLOWORLD]: meta_query\n");
return(TRUE);
}
C_DLLEXPORT int Meta_Attach(PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs)
{
if(!pFunctionTable)
{
return(FALSE);
}
memcpy(pFunctionTable, &gMetaFunctionTable, sizeof(META_FUNCTIONS));
gpMetaGlobals = pMGlobals;
ALERT(at_console, "[HELLOWORLD]: meta_attach\n");
return(TRUE);
}
C_DLLEXPORT int Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
ALERT(at_console, "[HELLOWORLD]: meta_detach\n");
return(TRUE);
}
int fnPre(char *s)
{
ALERT(at_console, "[HELLOWORLD]: model precaching\n");
RETURN_META_VALUE(MRES_IGNORED, 0);
}
enginefuncs_t my_tracers =
{
fnPre, // pfnPrecacheModel()
NULL, // pfnPrecacheSound()
NULL, // pfnSetModel()
NULL, // pfnModelIndex()
NULL, // pfnModelFrames()
NULL, // pfnSetSize()
NULL, // pfnChangeLevel()
NULL, // pfnGetSpawnParms()
NULL, // pfnSaveSpawnParms()
NULL, // pfnVecToYaw()
NULL, // pfnVecToAngles()
NULL, // pfnMoveToOrigin()
NULL, // pfnChangeYaw()
NULL, // pfnChangePitch()
NULL, // pfnFindEntityByString()
NULL, // pfnGetEntityIllum()
NULL, // pfnFindEntityInSphere()
NULL, // pfnFindClientInPVS()
NULL, // pfnEntitiesInPVS()
NULL, // pfnMakeVectors()
NULL, // pfnAngleVectors()
NULL, // pfnCreateEntity()
NULL, // pfnRemoveEntity()
NULL, // pfnCreateNamedEntity()
NULL, // pfnMakeStatic()
NULL, // pfnEntIsOnFloor()
NULL, // pfnDropToFloor()
NULL, // pfnWalkMove()
NULL, // pfnSetOrigin()
NULL, // pfnEmitSound()
NULL, // pfnEmitAmbientSound()
NULL, // pfnTraceLine()
NULL, // pfnTraceToss()
NULL, // pfnTraceMonsterHull()
NULL, // pfnTraceHull()
NULL, // pfnTraceModel()
NULL, // pfnTraceTexture()
NULL, // pfnTraceSphere()
NULL, // pfnGetAimVector()
NULL, // pfnServerCommand()
NULL, // pfnServerExecute()
NULL, // pfnClientCommand()
NULL, // pfnParticleEffect()
NULL, // pfnLightStyle()
NULL, // pfnDecalIndex()
NULL, // pfnPointContents()
NULL, // pfnMessageBegin()
NULL, // pfnMessageEnd()
NULL, // pfnWriteByte()
NULL, // pfnWriteChar()
NULL, // pfnWriteShort()
NULL, // pfnWriteLong()
NULL, // pfnWriteAngle()
NULL, // pfnWriteCoord()
NULL, // pfnWriteString()
NULL, // pfnWriteEntity()
NULL, // pfnCVarRegister()
NULL, // pfnCVarGetFloat()
NULL, // pfnCVarGetString()
NULL, // pfnCVarSetFloat()
NULL, // pfnCVarSetString()
NULL, // pfnAlertMessage()
NULL, // pfnEngineFprintf()
NULL, // pfnPvAllocEntPrivateData()
NULL, // pfnPvEntPrivateData()
NULL, // pfnFreeEntPrivateData()
NULL, // pfnSzFromIndex()
NULL, // pfnAllocString()
NULL, // pfnGetVarsOfEnt()
NULL, // pfnPEntityOfEntOffset()
NULL, // pfnEntOffsetOfPEntity()
NULL, // pfnIndexOfEdict()
NULL, // pfnPEntityOfEntIndex()
NULL, // pfnFindEntityByVars()
NULL, // pfnGetModelPtr()
NULL, // pfnRegUserMsg()
NULL, // pfnAnimationAutomove()
NULL, // pfnGetBonePosition()
NULL, // pfnFunctionFromName()
NULL, // pfnNameForFunction()
NULL, // pfnClientPrintf()
NULL, // pfnServerPrint()
NULL, // pfnCmd_Args()
NULL, // pfnCmd_Argv()
NULL, // pfnCmd_Argc()
NULL, // pfnGetAttachment()
NULL, // pfnCRC32_Init()
NULL, // pfnCRC32_ProcessBuffer()
NULL, // pfnCRC32_ProcessByte()
NULL, // pfnCRC32_Final()
NULL, // pfnRandomLong()
NULL, // pfnRandomFloat()
NULL, // pfnSetView()
NULL, // pfnTime()
NULL, // pfnCrosshairAngle()
NULL, // pfnLoadFileForMe()
NULL, // pfnFreeFile()
NULL, // pfnEndSection()
NULL, // pfnCompareFileTime()
NULL, // pfnGetGameDir()
NULL, // pfnCvar_RegisterVariable()
NULL, // pfnFadeClientVolume()
NULL, // pfnSetClientMaxspeed()
NULL, // pfnCreateFakeClient()
NULL, // pfnRunPlayerMove()
NULL, // pfnNumberOfEntities()
NULL, // pfnGetInfoKeyBuffer()
NULL, // pfnInfoKeyValue()
NULL, // pfnSetKeyValue()
NULL, // pfnSetClientKeyValue()
NULL, // pfnIsMapValid()
NULL, // pfnStaticDecal()
NULL, // pfnPrecacheGeneric()
NULL, // pfnGetPlayerUserId()
NULL, // pfnBuildSoundMsg()
NULL, // pfnIsDedicatedServer()
NULL, // pfnCVarGetPointer()
NULL, // pfnGetPlayerWONId()
NULL, // pfnInfo_RemoveKey()
NULL, // pfnGetPhysicsKeyValue()
NULL, // pfnSetPhysicsKeyValue()
NULL, // pfnGetPhysicsInfoString()
NULL, // pfnPrecacheEvent()
NULL, // pfnPlaybackEvent()
NULL, // pfnSetFatPVS()
NULL, // pfnSetFatPAS()
NULL, // pfnCheckVisibility()
NULL, // pfnDeltaSetField()
NULL, // pfnDeltaUnsetField()
NULL, // pfnDeltaAddEncoder()
NULL, // pfnGetCurrentPlayer()
NULL, // pfnCanSkipPlayer()
NULL, // pfnDeltaFindField()
NULL, // pfnDeltaSetFieldByIndex()
NULL, // pfnDeltaUnsetFieldByIndex()
NULL, // pfnSetGroupMask()
NULL, // pfnCreateInstancedBaseline()
NULL, // pfnCvar_DirectSet()
NULL, // pfnForceUnmodified()
NULL, // pfnGetPlayerStats()
NULL, // pfnAddServerCommand()
NULL, // pfnVoice_GetClientListening()
NULL, // pfnVoice_SetClientListening()
NULL, // pfnGetPlayerAuthId()
NULL, // pfnSequenceGet()
NULL, // pfnSequencePickSentence()
NULL, // pfnGetFileSize()
NULL, // pfnGetApproxWavePlayLen()
NULL, // pfnIsCareerMatch()
NULL, // pfnGetLocalizedStringLength()
NULL, // pfnRegisterTutorMessageShown()
NULL, // pfnGetTimesTutorMessageShown()
NULL, // pfnProcessTutorMessageDecayBuffer()
NULL, // pfnConstructTutorMessageDecayBuffer()
NULL, // pfnResetTutorMessageDecayData()
NULL, // pfnQueryClientCvarValue()
NULL, // pfnQueryClientCvarValue2()
};
C_DLLEXPORT int GetEngineFunctions(enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion)
{
if(!pengfuncsFromEngine)
{
return(FALSE);
} else if(*interfaceVersion != ENGINE_INTERFACE_VERSION)
{
*interfaceVersion = ENGINE_INTERFACE_VERSION;
return(FALSE);
}
memcpy(pengfuncsFromEngine, &my_tracers, sizeof(enginefuncs_t));
ALERT(at_console, "[HELLOWORLD]: transfer successfully ended\n");
return(TRUE);
}
После загрузки карты в консоли сервера вы увидете множество таких сообщений:
[HELLOWORLD]: model precaching
VI. TraceAPI
Исходники метамода включают в себя очень интересный плагин, который называется TraceAPI. Этот плагин - практическая реализации сказанного в данной статье. Данный плагин осуществляет обработку ВСЕХ доступных оригинальных функций(также и "post" функций). В оригинале:
This was originally intended as a (more or less) complete example of a Metamod plugin. It catches every call available to it (dll routines both before and after the game, as well as engine functions both before and after the engine).
Плагин получил свое название в связи с тем, что он отслеживает(ориг. "trace, tracing") вызовы оригинальный функций. Плагин удобно использовать для отслеживания активности Half-Life движка и его модов. Настоятельно рекомендую ознакомиться с листингом плагина, если вы серьезно решили заняться написанием плагина(ов) для metamod.
Список доступных кваров и команд:
- Серверные квары//-------------------------------------------------------------------
// Уровень логирования; большее значение соответствует большему количеству обрабатываемых процедур.
// Может принимать значения от 0 до 50.
// В "api_info.cpp" указан уровень логирования для различных функций.
//-------------------------------------------------------------------
// Уровень логирования для dllapi функций.
trace_dllapi
// Уровень логирования для "new" dllapi функций.
trace_newapi
// Уровень логирования для engine функций.
trace_engine
//-------------------------------------------------------------------
// Включает неограниченное логированние, однако за одну секунду обрабатывается
// только одна функция, дабы предотвратить перегрузку сервера.
// "1" - вкл,, "0" - выкл.(стандартное значение - "0")
trace_unlimit - Серверные команды// Включает обработку заданной функции, вне зависимости от уровня логирования("trace_*").
// В "api_info.cpp" указаны имена доступных функций. Регистр не имеет значения.
trace set <APIroutine>
//-------------------------------------------------------------------
// Отключает обработку заданной функции, если она была предварительно включена.
// Не влияет на функции, попадающие под действие уровня логирования("trace_*").
trace unset <APIroutine>
//-------------------------------------------------------------------
// Показывает обработанные оригинальные функции.
trace show
//-------------------------------------------------------------------
// Список доступных для отслеживания оригинальных функций.
trace list dllapi
trace list newapi
trace list engine
trace list all
//-------------------------------------------------------------------
// Версия плагина.
trace version
Небольшой комментарий автора:
Замечу, что в данном плагине при логировании функции информация, передаваемая пользователю, минимальна. Передается та информация, которую я посчитал нужным добавить(аргументы ClientCommand итд.). В некоторых случаях я недостаточно знал об этих функциях, чтобы добавить вывод необходимой информации(CreateBaseline итд.). Вы можете менять плагин как хотите и выводить любую интересующую вас информацию через логи; примеры должны давать вам практические знания. Я был бы не против добавить стоящие поправки в распространяемый вариант.
В оригинале:
Note the information it logs on each routine invocation is, at the moment, relatively minimal. I included information that seemed obvious (args for a ClientCommand, etc), and I've added info for other routines as I've come across a need. Most routines I still know too little about to log any particular information (CreateBaseline, etc). Feel free to add information that you're interested in to the log messages in the routines; the examples should be pretty self-explanatory. I'd be interested in knowing as well, for adding it to the distribution code.
В следующей главе будет рассмотрена тема использования кваров, команд, меню и пр. Просьба тут оставлять комментарии и вопросы ТОЛЬКО к статье, а все вопросы по кодингу в соответствующем разделе.
tags/тэги: metamod, кодинг, плагины, таблицы функций, создание, написание