Gratt
Модостроитель
- Регистрация
- 14 Ноя 2014
- Сообщения
- 3.301
- Благодарности
- 4.636
- Баллы
- 625
* если после темы останутся вопросы - спрашиваем. Потом сделаем FAQ.
Классы перехватчики
Шаблон перехвата средствами CInvoke
Шаблон перехвата с помощью макросов HOOK AS
Перехват обычной функции
Перехват функции-члена
Множественный перехват функции
Защищенный хук
Замена хука
Неактивный хук
Комбинирование флагов
Классы перехватчики
CCallBack - весь процесс перехвата и управления хуками реализован в нем. Класс контролирует, чтобы программист не допустил критических ошибок, а также регулирует множественные перехваты.
CInvoke - интерфейс перехвата. Чтобы не выполнять рутинную работу, этот класс внутри своих экземпляров строит запросы в CCallBack основываясь на базовых входных данных.
Шаблон перехвата средствами CInvoke
Повторюсь, - CInvoke строит запросы за программиста. Экземпляру необходимо передать сведения об указателе на функцию (тип), адрес оригинальной функции и куда ее переадресовать.
Любой хук имеет вид:
где:
Любой хук имеет вид:
C++:
CInvoke<pointer-type> Ivk_<hook-name>( original-pointer, redirect-pointer, hook-flags );
Код:
pointer-type - тип указателя на оригинальную функцию. Через него мы сможем обращаться к оригинальной функции. Как строить указатель см. в теме 6.1.
hook-name - любое удобное имя хука. Я обычно предпочитаю вид Ivk_<class-name>_<method-name>, например Ivk_oCGame_Render.
original-pointer - адрес на перехвачиваемую функцию или метод.
redirect-pointer - адрес на переадресованную функцию.
hook-flags - флаги перехвата. По умолчанию используется IVK_AUTO.
IVK_DISABLED Не дает хуку перехватить функцию. Делает экземпляр обычным указателем.
IVK_READONLY Аналогично предыдущему.
IVK_NORMAL Обычный хук. Если существует хотя бы одна перехваченная функция по тому же адресу - выскочит ошибка о невозможности перехвата.
IVK_AUTO Позволяет хукам создавать дерево перехвата по одному и тому же адресу. redirect-pointer будут вызываться поочереди, начиная с последнего за каждым return original-pointer.
IVK_REDEFINE Перезаписывает собой любой существующий по адресу хук, либо при отсутствии просто перехватывает функцию. Перезаписанный хук деактивируется.
IVK_PROTECTED Ставит защиту на хук. Рекомендуется использовать только в очень важных функциях, поскольку такой хук невозможно отключить, к нему невозможно подключиться, его невозможно перезаписать.
Шаблон перехвата с помощью макросов HOOK AS
Тут все еще проще. Это тот же CInvoke, только с двумя макросами сверху. За основу берется функция Intercept, в шаблоне которой автоматически определяется тип указателя.
C++:
HOOK Ivk_<hook-name> AS( original-pointer, redirect-pointer, hook-flags );
// расширяется до:
// auto Ivk_<hook-name> = InvokeAuto( original-pointer, redirect-pointer, hook-flags );
Перехват обычной функции
Открываем GothicNames и выбираем функцию. Пусть это будет событие, которое применяет выбранные настройки видео.
C++:
// 0x0042C450 int __cdecl Apply_Options_Video(void)
// прототип функции переадресации
int Apply_Options_Video();
// В <> указываем тип указателя оригинальной функции
CInvoke<int(*)()> Ivk_Apply_Options_Video( 0x0042C450, &Apply_Options_Video );
// реализация функции переадресации
int Apply_Options_Video() {
Message::Info( "Попытка применить изменения . . ." );
// Обращаемся к экземпляру хука как к функции.
// Так будет вызвана оригинальная функция.
// Оригинальная функция выполнит применение настроек видео и вернет значение в result.
int Result = Ivk_Apply_Options_Video();
Message::Info( "Изменения применены с кодом " + string::Hex32( Result ) );
// Возвращаем значение в движок, полученное из оригинальной функции.
return Result;
}
Перехват функции-члена
Рассмотрим вариант с функцией отрисовки воба. Покажу сразу несколько вариантов реализации.
Вариант #1
Переадресация метода в обычную функцию.
Вариант #2
переадресация метода в метод.
Для данного примера идем в Gothic API и в классе zCVob создаем новую функцию:
int __fastcall RenderUnion(struct zTRenderContext &);
Вариант #3
переадресация метода в метод с указателем на конкретную функци-член.
Для данного примера идем в Gothic API и в классе zCVob создаем новую функцию:
int __fastcall RenderUnion( struct zTRenderContext & );
Вариант #1
Переадресация метода в обычную функцию.
C++:
// 0x006015D0 public: virtual int __fastcall zCVob::Render(struct zTRenderContext &)
int __fastcall zCVob_Render( zCVob* _this, zTRenderContext& context );
CInvoke<int(__fastcall *)( zCVob* _this, zTRenderContext& context )> Ivk_zCVob_Render( 0x006015D0, &zCVob_Render );
int __fastcall zCVob_Render( zCVob* _this, zTRenderContext& context ) {
if( _this == player ) {
// Если в данный момент рендерится модель игрока, то на экран
// будет выведено соответствующее сообщение
screen->PrintCX( 1000, "Сейчас на экран рисуется player." );
}
// Отрабатываем и возвращаем оригинальный результат
return Ivk_zCVob_Render( _this, context );
}
Вариант #2
переадресация метода в метод.
Для данного примера идем в Gothic API и в классе zCVob создаем новую функцию:
int __fastcall RenderUnion(struct zTRenderContext &);
C++:
// 0x006015D0 public: virtual int __fastcall zCVob::Render(struct zTRenderContext &)
CInvoke<int(__fastcall *)( zCVob* _this, zTRenderContext& context )> Ivk_zCVob_Render( 0x006015D0, &zCVob::RenderUnion );
// Прототип уже указан в классе. __fastcall не обязателен,
// так как он уже определен в прототипе.
int zCVob::RenderUnion( zTRenderContext& context ) {
if( this == player ) {
// Если в данный момент рендерится модель игрока, то на экран
// будет выведено соответствующее сообщение
screen->PrintCX( 1000, "Сейчас на экран рисуется player." );
}
// Отрабатываем и возвращаем оригинальный результат
return Ivk_zCVob_Render( this, context );
}
Вариант #3
переадресация метода в метод с указателем на конкретную функци-член.
Для данного примера идем в Gothic API и в классе zCVob создаем новую функцию:
int __fastcall RenderUnion( struct zTRenderContext & );
C++:
// 0x006015D0 public: virtual int __fastcall zCVob::Render(struct zTRenderContext &)
// this не указываем, он предполагается типом.
CInvoke<int(__fastcall zCVob::*)( zTRenderContext& context )> Ivk_zCVob_Render( 0x006015D0, &zCVob::RenderUnion );
int zCVob::RenderUnion( zTRenderContext& context ) {
if( this == player ) {
screen->PrintCX( 1000, "Сейчас на экран рисуется player." );
}
// this передается оператором -> на указатель. см тему 6.1.
return ( this->*Ivk_zCVob_Render )( context );
}
Множественный перехват функции
Далее можно поэкспериментировать в нескольких плагинах. Я же покажу в рамках одного, разницы никакой.
Идем в zCVob и пишем еще одну функцию ниже предыдущей.
int __fastcall RenderUnion_2( struct zTRenderContext & );
Дополнительно покажу как хукать конструкцией HOOK AS
Идем в zCVob и пишем еще одну функцию ниже предыдущей.
int __fastcall RenderUnion_2( struct zTRenderContext & );
Дополнительно покажу как хукать конструкцией HOOK AS
C++:
// 0x006015D0 public: virtual int __fastcall zCVob::Render(struct zTRenderContext &)
HOOK Ivk_zCVob_Render AS( 0x006015D0, &zCVob::RenderUnion ); // Этот хук переватывает первым
HOOK Ivk_zCVob_Render_2 AS( 0x006015D0, &zCVob::RenderUnion_2 ); // А это вторым. Но сначала отработает он.
int zCVob::RenderUnion( zTRenderContext& context ) {
if( this == player ) {
screen->PrintCX( 1000, "Сейчас на экран рисуется player." );
}
return ( this->*Ivk_zCVob_Render )( context );
}
int zCVob::RenderUnion_2( zTRenderContext& context ) {
if( this == player ) {
screen->PrintCX( 1200, "А это сообщение говорит о том, что два хука уживаются в одной функции." );
}
return ( this->*Ivk_zCVob_Render_2 )( context );
}
Защищенный хук
Вообще не рекомендую баловаться такими хуками, если только прям очень нужно.
За защиту хука отвечает флаг IVK_PROTECTED, он запретит любые манипуляции над всей веткой хуков.
Пусть в этот раз будет функция проигрывания видеоролика.
За защиту хука отвечает флаг IVK_PROTECTED, он запретит любые манипуляции над всей веткой хуков.
Пусть в этот раз будет функция проигрывания видеоролика.
C++:
// 0x0042B940 public: int __thiscall CGameManager::PlayVideo(class zSTRING)
int __fastcall CGameManager_PlayVideo ( CGameManager* _this, void* vtable, zSTRING );
int __fastcall CGameManager_PlayVideo_2( CGameManager* _this, void* vtable, zSTRING );
CInvoke<int(__thiscall*)( CGameManager* _this, zSTRING )> Ivk_CGameManager_PlayVideo ( 0x0042B940, &CGameManager_PlayVideo, IVK_PROTECTED );
CInvoke<int(__thiscall*)( CGameManager* _this, zSTRING )> Ivk_CGameManager_PlayVideo_2( 0x0042B940, &CGameManager_PlayVideo_2 ); // Тут нам выбьет ошибку. Перехват невозможен.
int __fastcall CGameManager_PlayVideo( CGameManager* _this, void* vtable, zSTRING videoName ) {
int Result = Ivk_CGameManager_PlayVideo( _this, videoName );
Message::Info( "Функция (#1) завершена с кодом " + string::Hex32( Result ) );
return Result;
}
int __fastcall CGameManager_PlayVideo_2( CGameManager* _this, void* vtable, zSTRING videoName ) {
int Result = Ivk_CGameManager_PlayVideo( _this, videoName );
Message::Info( "Функция (#2) завершена с кодом " + string::Hex32( Result ) );
return Result;
}
Замена хука
Все тот же пример, но без защиты. Второй хук будет иметь флаг перезаписи.
Такая конструкция выключит первый хук, а на его место поставит второй.
Выполнится функция CGameManager_PlayVideo_2.
Такая конструкция выключит первый хук, а на его место поставит второй.
Выполнится функция CGameManager_PlayVideo_2.
C++:
// 0x006660E0 public: virtual void __thiscall oCNpc::OnDamage(struct oCNpc::oSDamageDescriptor &)
void __fastcall oCNpc_OnDamage_1( oCNpc*, void*, oCNpc::oSDamageDescriptor& );
void __fastcall oCNpc_OnDamage_2( oCNpc*, void*, oCNpc::oSDamageDescriptor& );
CInvoke<void( __thiscall* )( oCNpc*, oCNpc::oSDamageDescriptor& )> Ivk_oCNpc_OnDamage_1( 0x006660E0, &oCNpc_OnDamage_1 );
CInvoke<void( __thiscall* )( oCNpc*, oCNpc::oSDamageDescriptor& )> Ivk_oCNpc_OnDamage_2( 0x006660E0, &oCNpc_OnDamage_2, IVK_REDEFINE ); // Этот хук заменит первый
void __fastcall oCNpc_OnDamage_1( oCNpc* _this, void* vtable, oCNpc::oSDamageDescriptor& damdesc ) {
Message::Info( "Сообщение об уроне #1" );
Ivk_oCNpc_OnDamage_1( _this, damdesc );
}
void __fastcall oCNpc_OnDamage_2( oCNpc* _this, void* vtable, oCNpc::oSDamageDescriptor& damdesc ) { // В игре будет выведено только это сообщение
Message::Info( "Сообщение об уроне #2" );
Ivk_oCNpc_OnDamage_2( _this, damdesc );
}
Неактивный хук
Хук можно быстро сделать неактивным еще на этапе его построения.
Для этого указываем флаг IVK_DISABLED (или IVK_READONLY)
В примере ниже ни один из хуков не будет выполнен.
Для этого указываем флаг IVK_DISABLED (или IVK_READONLY)
В примере ниже ни один из хуков не будет выполнен.
C++:
// 0x0042D710 int __cdecl Apply_Options_Game(void)
int Apply_Options_Game();
CInvoke<int( *)( )> Ivk_Apply_Options_Game( 0x0042D710, Apply_Options_Game, IVK_DISABLED );
int Apply_Options_Game() {
Message::Info( "Применение игровых настроек . . ." );
int Result = Ivk_Apply_Options_Game();
Message::Box( "Функция завершилась с кодом " + string::Hex32( Result ) );
return Result;
}
Комбинирование флагов
Флаги можно комбинировать. например сделать перезапись хука и моментальную его защиту.
В примере ниже второй хук перезапишет первый. Далее его будет невозможно ни изменить, ни удалить, ни подключиться к нему.
В примере ниже второй хук перезапишет первый. Далее его будет невозможно ни изменить, ни удалить, ни подключиться к нему.
C++:
// 0x007323C0 public: void __thiscall oCNpc::EquipItem(class oCItem *)
void __fastcall oCNpc_EquipItem_1( oCNpc*, void*, oCItem* );
void __fastcall oCNpc_EquipItem_2( oCNpc*, void*, oCItem* );
void __fastcall oCNpc_EquipItem_3( oCNpc*, void*, oCItem* );
CInvoke<void( __thiscall* )( oCNpc*, oCItem* )> Ivk_oCNpc_EquipItem_1( 0x007323C0, oCNpc_EquipItem_1 );
CInvoke<void( __thiscall* )( oCNpc*, oCItem* )> Ivk_oCNpc_EquipItem_2( 0x007323C0, oCNpc_EquipItem_2, IVK_REDEFINE | IVK_PROTECTED ); // хук заменит первый
CInvoke<void( __thiscall* )( oCNpc*, oCItem* )> Ivk_oCNpc_EquipItem_3( 0x007323C0, oCNpc_EquipItem_3, IVK_REDEFINE ); // доступ будет невозмжен, так как предыдущий защищен
void __fastcall oCNpc_EquipItem_1( oCNpc* _this, void* vtable, oCItem* item ) {
Message::Info( "OnEquip #1" );
Ivk_oCNpc_EquipItem_1( _this, item );
}
void __fastcall oCNpc_EquipItem_2( oCNpc* _this, void* vtable, oCItem* item ) {
Message::Info( "OnEquip #2" );
Ivk_oCNpc_EquipItem_2( _this, item );
}
void __fastcall oCNpc_EquipItem_3( oCNpc* _this, void* vtable, oCItem* item ) {
Message::Info( "OnEquip #3" );
Ivk_oCNpc_EquipItem_3( _this, item );
}
Последнее редактирование: