May 302013
 

Автор: joaquimandrade

Модуль Orpheu дает скриптеру возможность использовать функции, которые было бы невозможно использовать без него (включая функции от других плагинов MetaMod).Модуль работает, как на Windows, так и на Linux.
Начиная с версии 2.1 ( 29 января 2010 г.), Orpheu также поддерживает прямой доступ к памяти.

Суть работы заключается в следующем:
Модуль использует преобразования между типами С++ и Pawn, и позволяет вызывать функции, указав ее адрес в памяти, а также используемые ею аргументы. Этот метод включает в себя общие принципы работы со структурами С++.

Например, предположим, что вы хотите использовать эту функцию:
void PM_Move( struct playermove_s *ppmove, qboolean server)

Она получает один аргумент типа “playermove_s” и другой типа “qboolean”. Значения эта функция не возвращает.

Пока типы, необходимые функции, используются в модуле, вы можете использовать ее. Если один из типов не используется, то Вы можете подключить его напрямую, и все равно продолжать работать с функцией, к примеру подключать ее, так как при подключении функции Вам не надо заботиться обо всех ее аргументах.

Существует несколько способов поиска функций в библиотеках:

  • путем поиска по имени: в библиотеках, компилированных в Linux, символьные имена функций закреплены за самими функциями. К несчастью, этого не происходит, если библиотека скомпилирована для Windows, что делает этот способ бесполезным, если у Вас нет исходника библиотеки и Вы не можете скомпилировать его самостоятельно так, чтобы он передавал символьные имена для функций. В этой статье описаны методы для использования функций модуля MonsterMod.
  • путем нахождения набора байтов в памяти, относящихся конкретно к данной функции. Данный метод называется «сканированием сигнатур» (“signature scanning”). В общем об этом методе написано здесь, более подробно здесь и здесь. В общем, этот метод позволяет обращаться к любой функции в памяти, как только вы найдёте ее. Чтобы толком разобраться в этом, вам необходимо узнать немного больше об этом методе, что вы можете сделать по ссылкам выше. (Прим. перевод.: На нашем сайте эти статьи в скором времени будут переведены, как только мы это сделаем, заменим ссылки)
  • путем получения адресов во время использования плагина. Если вы можете найти адрес функции программно, то вы можете использовать этот вариант. Это возможно, благодаря использованием модулем основных функций движка. Эти функции предусмотрены в AMXModX и без этого модуля, однако при его использовании, вы можете перехватить эти адреса в большинстве случаев (например, когда функции вызываются другими плагинами MetaMod).

Добавлено в 2.1

  • путем поиска оффсетов функций в библиотеке. Этот метод используется для быстрого тестирования и на него не следует полагаться, так как найденные оффсеты могут оказаться неправильными при обновлении или изменение библиотеки.
  • путем нахождения указателя функции в виртуальной таблице функций класса. Этот способ позволяет использовать множество функций, путем обнаружения всего лишь указателя. Метод был взят из Hamsandwich и переделан для использования в Orpheu. Он дает возможность захвата виртуальной функции энтити и объекта, например, CGameRules. В отличии от версии Hamsandwich добавлено следующее: теперь набор кодированных напрямую функций не ограничен, а также Вы можете захватывать некоторые зависящие от мода функции, которые не могли быть захвачены ранее.

Чтобы передать модулю информацию о функции, вы должны создать файл, отформатированный в соответствие стандарту JSON и поместить в папку “configs/orpheu/functions”.

Для функции:

CMBaseMonster* spawn_monster(int monster_type, Vector& origin, float angle, int respawn_index)

Содержимое файла будет таким:

{
    "name" : "spawn_monster",
    "library" : "monstermod",
    "info" : "Spawns a monstermod monster in the world",
    "arguments" :
    [
        {
            "type" : "int",
            "info" : "The type of the monster"
        },
        {
            "type" : "Vector &",
            "info" : "The position where the monster will be placed at"
        },
        {
            "type" : "float",
            "info" : "Angle of the monster"
        },
        {
            "type" : "int",
            "info" : "Respawn index. Related to the order of a monster respawning if failed to spawn at the first try"
        }
    ],
    "return" :
    {
        "type" : "CMBaseMonster *",
        "info" : "The monster created"
    },
    "identifiers":
    [
        {
            "os" : "windows",
            "value" : "[email protected]@[email protected]@[email protected]@[email protected]"
        },
        {
            "os" : "linux",
            "value" : "_Z13spawn_monsteriR6Vectorfi"
        }
    ]
}
  • поля “info” не обязательны;
  • поле “name” должно совпадать с именем фала;
  • имя библиотеки “mod” для модов, как cstrike, и “engine” для dll движка. Для библиотек MetaMod вам необходимо создать файл, как тот, что Вы можете найти в директории “configs\orpheu\libraries”, что содержит пару libraryname/libraryCvar, таким образом модуль распознается, как один из модулей Cvar.
  • “identidiers” – это список групп “os”/”value” для идентификации функции. В случае, если библиотека – “mod”, необходимо добавить дополнительное поле “mod”. Пусть смысл этого поля исключительно формален, однако само поле обязательно. Оно должно выглядеть так:
    "mod" : "cstrike"

В этом случае для ссылки на функцию используется метод определения имени. В случае использования сигнатур, поле “value” будет представлено последовательностью байтов, “*” или “?”, например:
"value" : [0x1,"*","?"]

“*” необходимо использовать в том случае, если значение этих байтов не важно.

То есть значение
"value" : [0x1,"*"]
равносильно
[0x1,0x0] , [0x1,0x1] , ... [0x1,0xFF].

“?” необходимо использовать, если не важно не только значение этих байтов, но и само их существование.
То есть
"value" : [0x1,"?"]
предполагает значения
[0x1] , [0x1,0x0] , [0x1,0x1] , ... [0x1,0xFF].

JSON – это широко распространенный тип файлов. Чтобы убедиться в том, что файл корректно отформатирован, вы можете использовать валидатор. В этом проекте я немного изменил библиотеку для того, чтобы JSON мог прочитать байты. Это делается для проверки файлов, не содержащих сигнатуры.

Отдельно, стоит упомянуть о функциях, принадлежащих к классу, например:

void CMController :: HandleAnimEvent( MonsterEvent_t *pEvent )  

В данном случае необходимо создать папку под именем “CMController”, а в ней файл “HandleAnimEvent”. Это обязательно и необходимо для более понятной организации. Вам также необходимо добавить в файл поле “class”. В итоге файл будет выглядеть так:

{
    "name" : "HandleAnimEvent",
    "class" : "CMController",
    "library" : "monstermod",
    "arguments" :
    [
        {
            "type" : "MonsterEvent_t *"
        }
    ],
    "identifiers":
    [
        {
            "os" : "windows",
            "value" : "[email protected]@@[email protected]@@Z"
        },
        {
            "os" : "linux",
            "value" : "_ZN12CMController15HandleAnimEventEP14MonsterEvent_t"
        }
    ]
}

Другой метод использования функций имеет дело с получением адреса программно. Взгляните на http://metamod.org/sdk/dox/eiface_8h-source.html – строка 100. Здесь представлена структура “enginefuncs_s”. Эта структура содержит адреса функций движка. Модуль имеет возможность оперировать ей. Скажем, что мы хватим захватить функцию расположенную в структуре:

void (*pfnServerPrint)( const char *szMsg );  

Необходимо создать такой файл:

{
    "name" : "ServerPrint",
    "library" : "engine",
    "arguments" :
    [
        {
            "type" : "char *"
        }
    ]
}

Идентификаторы не нужны, так как адрес будет найден во время запуска, программно. В плагине появится:

native OrpheuFunction:OrpheuCreateFunction(address,libFunctionName[],classname[]="")  

Затем вы можете захватить или вызвать ее. При захвате функции, Вы можете перехватить сообщение консоли такое же, что появляется при использовании серверной команды “metamod”.
Это один из вариантов (кодированный вручную) метода, использующего функцию, адрес, который есть в плагине. Другой вариант – использовать OrpheuGetDLLFunction для восстановления функций из структуры DLL_FUNCTIONS. http://metamod.org/sdk/dox/eiface_8h-source.html – строка 384. Для обычных случаев используйте native:
native OrpheuFunction:OrpheuCreateFunction(address,libFunctionName[],classname[]=””)

Модуль позволяет вам управлять структурами обычным путем. Скажем, что нам необходимо захватить функцию PM_Move, которая может быть найдена в структуре DLL_FUNCTIONS. Файл:

{
    "name" : "PM_Move",
    "library" : "mod",
    "arguments" :
    [
        {
            "type" : "playermove_s *"
        },
        {
            "type" : "qboolean"
        }
    ]
}

Захват:

public plugin_init() 
{ 
    new OrpheuFunction:PM_Move = OrpheuGetDLLFunction("pfnPM_Move","PM_Move") 
    OrpheuRegisterHook(PM_Move,"OnPM_Move") 
} 

public OnPM_Move(ppmove,server) 
{ 
     
}  

Первый аргумент функции – структура. Вы можете увидеть это здесь. http://metamod.org/sdk/dox/eiface_8h-source.html – строка 92. Далее имеем дело с данными структур:

public OnPM_Move(ppmove,server) 
{ 
    new id = OrpheuGetParamStructMember(1,"player_index") + 1 
    new Float:friction = OrpheuGetParamStructMember(1,"friction") 
     
    new sztexturename[20] 
    OrpheuGetParamStructMember(1,"sztexturename",sztexturename,charsmax(sztexturename)) 
     
    server_print("ID %d friction %f texturename %s",id,friction,sztexturename) 
}  

Большую информацию по работе со структурами смотрите по ссылкам. Те же структуры содержат адреса для других функций. Ниже описан пример как захватить одну из них:

{
    "name" : "PM_PlaySound",
    "library" : "engine",
    "arguments" :
    [
        {
            "type" : "int"
        },
        {
            "type" : "char *"
        },
        {
            "type" : "float"
        },
        {
            "type" : "float"
        },
        {
            "type" : "int"
        },
        {
            "type" : "int"
        }
    ]
}
new OrpheuHook:PM_PlaySoundHook 

public plugin_init() 
{ 
    new OrpheuFunction:PM_Move = OrpheuGetDLLFunction("pfnPM_Move","PM_Move") 
    OrpheuRegisterHook(PM_Move,"OnPM_Move") 
    OrpheuRegisterHook(PM_Move,"OnPM_MovePost",OrpheuHookPost) 
} 

public OnPM_Move(ppmove,server) 
{ 
    // Retrieves the address of the function to hook 
    new PM_PlaySoundAddress = OrpheuGetParamStructMember(1,"PM_PlaySound") 
     
    // Creates the function in the module  
    new OrpheuFunction:PM_PlaySound = OrpheuCreateFunction(PM_PlaySoundAddress,"PM_PlaySound") 
     
    // Hooks it 
    PM_PlaySoundHook = OrpheuRegisterHook(PM_PlaySound,"OnPM_PlaySoundHook") 
} 

public OnPM_PlaySoundHook(channel,sample[],Float:volume,Float:attenuation,fFlags,pitch) 
{ 
    server_print("Sample %s",sample) 
} 

public OnPM_MovePost(ppmove,server) 
{ 
    OrpheuUnregisterHook(PM_PlaySoundHook) 
}  

Список поддерживаемых типов данных:

    "bool"
    "byte"
    "long"
    "CBaseEntity *"
    "char *"
    "edict_s *"
    "float"
    "Vector *"
    "CMBaseMonster *"
    "char"
    "short"
    "entvars_s *"

Список поддерживаемых структур:

    "movevars_s *"
    "usercmd_s *"
    "MonsterEvent_t *"
    "DLL_FUNCTIONS *"
    "playermove_s *"
    "enginefuncs_t *"
    "TraceResult *"
    "physent_s *"
    "pmplane_s *"
    "pmtrace_s *"
    "weapon_data_s *"
    "AmmoInfo *"
    "ItemInfo *"
    "Task_t *"
    "Schedule_t *"

Я добавлю несколько практических примеров позже. Если модуль не запускается на вашем устройстве Linux, то вам следует установить libstdc++.
Следующий материал действителен для Orpheu 2.1 и выше.
В версии 2.1 была добавлена поддержка виртуальных функций (основано на Hamsandwich) и поиска/исправления памяти (основано на Mem Hack).

Виртуальные функции:
Виртуальные функции – это функции, предоставляющие совместные ресурсы для нескольких различных классов (как Spawn) и, соответственно, по другому осуществляющие это предоставление. Способ, которым функции компилируются, позволяет размещать их в памяти, путем обеспечения их простыми числовыми оффсетами. Ссылки на функции хранятся в таблице так, что каждый объект класса может взаимодействовать с ними.
Эта таблица для мода Counter Strike, восстановленная из собственного Linux-файла класса CHalfLifeMultiplay для CGameRules. (Каждая строка содержит символьное имя представляемой функции):

Think__18CHalfLifeMultiplay 
IsAllowedToSpawn__18CHalfLifeMultiplayP11CBaseEntity  
FAllowFlashlight__18CHalfLifeMultiplay 
FShouldSwitchWeapon__18CHalfLifeMultiplayP11CBasePlayerP15CBasePlayerItem 
GetNextBestWeapon__18CHalfLifeMultiplayP11CBasePlayerP15CBasePlayerItem 
IsMultiplayer__18CHalfLifeMultiplay 
IsDeathmatch__18CHalfLifeMultiplay 
IsTeamplay__10CGameRules 
IsCoOp__18CHalfLifeMultiplay 
GetGameDescription__10CGameRules 
ClientConnected__18CHalfLifeMultiplayP7edict_sPCcT2Pc 
InitHUD__18CHalfLifeMultiplayP11CBasePlayer 
ClientDisconnected__18CHalfLifeMultiplayP7edict_s 
UpdateGameMode__18CHalfLifeMultiplayP11CBasePlayer 
FlPlayerFallDamage__18CHalfLifeMultiplayP11CBasePlayer 
FPlayerCanTakeDamage__18CHalfLifeMultiplayP11CBasePlayerP11CBaseEntity 
ShouldAutoAim__10CGameRulesP11CBasePlayerP7edict_s 
PlayerSpawn__18CHalfLifeMultiplayP11CBasePlayer 
PlayerThink__18CHalfLifeMultiplayP11CBasePlayer 
FPlayerCanRespawn__18CHalfLifeMultiplayP11CBasePlayer 
FlPlayerSpawnTime__18CHalfLifeMultiplayP11CBasePlayer 
GetPlayerSpawnSpot__18CHalfLifeMultiplayP11CBasePlayer 
AllowAutoTargetCrosshair__18CHalfLifeMultiplay 
ClientCommand_DeadOrAlive__18CHalfLifeMultiplayP11CBasePlayerPCc 
ClientCommand__18CHalfLifeMultiplayP11CBasePlayerPCc 
ClientUserInfoChanged__18CHalfLifeMultiplayP11CBasePlayerPc 
IPointsForKill__18CHalfLifeMultiplayP11CBasePlayerT1 
PlayerKilled__18CHalfLifeMultiplayP11CBasePlayerP9entvars_sT2 
DeathNotice__18CHalfLifeMultiplayP11CBasePlayerP9entvars_sT2 
CanHavePlayerItem__18CHalfLifeMultiplayP11CBasePlayerP15CBasePlayerItem 
PlayerGotWeapon__18CHalfLifeMultiplayP11CBasePlayerP15CBasePlayerItem 
WeaponShouldRespawn__18CHalfLifeMultiplayP15CBasePlayerItem 
FlWeaponRespawnTime__18CHalfLifeMultiplayP15CBasePlayerItem 
FlWeaponTryRespawn__18CHalfLifeMultiplayP15CBasePlayerItem 
VecWeaponRespawnSpot__18CHalfLifeMultiplayP15CBasePlayerItem 
CanHaveItem__18CHalfLifeMultiplayP11CBasePlayerP5CItem 
PlayerGotItem__18CHalfLifeMultiplayP11CBasePlayerP5CItem 
ItemShouldRespawn__18CHalfLifeMultiplayP5CItem 
FlItemRespawnTime__18CHalfLifeMultiplayP5CItem 
VecItemRespawnSpot__18CHalfLifeMultiplayP5CItem 
CanHaveAmmo__10CGameRulesP11CBasePlayerPCci 
PlayerGotAmmo__18CHalfLifeMultiplayP11CBasePlayerPci 
AmmoShouldRespawn__18CHalfLifeMultiplayP15CBasePlayerAmmo 
FlAmmoRespawnTime__18CHalfLifeMultiplayP15CBasePlayerAmmo 
VecAmmoRespawnSpot__18CHalfLifeMultiplayP15CBasePlayerAmmo 
FlHealthChargerRechargeTime__18CHalfLifeMultiplay 
FlHEVChargerRechargeTime__18CHalfLifeMultiplay 
DeadPlayerWeapons__18CHalfLifeMultiplayP11CBasePlayer 
DeadPlayerAmmo__18CHalfLifeMultiplayP11CBasePlayer 
GetTeamID__18CHalfLifeMultiplayP11CBaseEntity 
PlayerRelationship__18CHalfLifeMultiplayP11CBasePlayerP11CBaseEntity 
GetTeamIndex__10CGameRulesPCc 
GetIndexedTeamName__10CGameRulesi 
IsValidTeam__10CGameRulesPCc 
ChangePlayerTeam__10CGameRulesP11CBasePlayerPCcii 
SetDefaultPlayerTeam__10CGameRulesP11CBasePlayer 
PlayTextureSounds__18CHalfLifeMultiplay 
FAllowMonsters__18CHalfLifeMultiplay 
EndMultiplayerGame__18CHalfLifeMultiplay 
IsFreezePeriod__10CGameRules 
ServerDeactivate__18CHalfLifeMultiplay 
CheckMapConditions__18CHalfLifeMultiplay 
CleanUpMap__18CHalfLifeMultiplay 
RestartRound__18CHalfLifeMultiplay 
CheckWinConditions__18CHalfLifeMultiplay 
RemoveGuns__18CHalfLifeMultiplay 
GiveC4__18CHalfLifeMultiplay 
ChangeLevel__18CHalfLifeMultiplay 
GoToIntermission__18CHalfLifeMultiplay  

Чтобы использовать одну из них вам необходимо создать файл, который должен выглядеть так:

{
    "name" : "GetNextBestWeapon",
    "class" : "CGameRules",
    "library" : "mod",
    "arguments" : 
    [
        {
            "type" : "CBasePlayer *"
        },
        {
            "type" : "CBasePlayerItem *"
        }
    ],
    "return" :
    {
        "type" : "bool"
    },
    "indexes" : 
    [
        {
            "os" : "windows",
            "mod" : "cstrike",
            "value" : 5
        },
        {
            "os" : "linux",
            "mod" : "cstrike",
            "value" : 6
        }
    ]
}

(В Linux виртуальная таблица включает дополнительную функцию в начале, так что оффсеты должны быть увеличены на единицу)

Это описание функции должно быть размещено в каталоге “VirtuаlFunctions” в папке “CGameRules” в файле “GetNextBestWeapon”.

Теперь вы можете использовать эти функции в плагине несколькими командами, выглядящими так:

OrpheuGetFunctionFromClass(entityClassName[],libFunctionName[],libClassName[]) 
OrpheuGetFunctionFromEntity(id,libFunctionName[],libClassName[]) 
OrpheuGetFunctionFromObject(object,libFunctionName[],libClassName[]) 
OrpheuGetFunctionFromMonster(id,libFunctionName[],libClassName[])  

Первые две работают точно также как и в Hamsandwich:

  • Первая вызывает функцию, основанную на классе энтити.
  • Вторая вызывает функцию, основанную на самом энтити.

Для использования функций из CHalfLifeMultiplay, описанных выше, нам необходим нативная функция OrpheuGetFunctionFromObject. Эта функция вызывает функцию, базированную на объекте, класс которого вам необходим, таким образом нам необходим дополнительный шаг для получения объекта. Это один из путей решения для этого частного случая:

{ 
    "name" : "InstallGameRules", 
    "library" : "mod", 
    "return" :  
    { 
        "type" : "CHalfLifeMultiplay *" 
    }, 
    "identifiers": 
    [ 
        { 
            "os" : "windows", 
            "mod" : "cstrike", 
            "value" : [0x68,0x8c,0xea,"*",0xa,0xff,0x15,0xdc,0x23,"*",0xa,0x83,0xc4,0x4,0xff,0x15,0xe0,0x23,"*",0xa,0xa1,0xb8,0x25] 
        }, 
        { 
            "os" : "linux", 
            "mod" : "cstrike", 
            "value" : "InstallGameRules__Fv" 
        } 
    ] 
}  
public plugin_precache() 
{     
    new OrpheuFunction:InstallGameRules = OrpheuGetFunction("InstallGameRules") 
    OrpheuRegisterHook(InstallGameRules,"OnInstallGameRules",OrpheuHookPost) 
} 

public OnInstallGameRules() 
{ 
    new obj = OrpheuGetReturn()     
    new OrpheuFunction:GetNextBestWeapon = OrpheuGetFunctionFromObject(obj,"GetNextBestWeapon","CGameRules") 
    OrpheuRegisterHook(GetNextBestWeapon,"OnGetNextBestWeapon") 
} 

public OnGetNextBestWeapon(obj,id,weaponID) 
{ 
    //     
}  

Управление памятью
Модуль теперь имеет возможность поиска и установления значения напрямую в любую позицию в памяти, где хранится библиотека. Например, таким образом можно поменять стоимость оружия, заменить строки, чтобы изменить текст появляющийся в консоли. Это может быть сделано, используя оффсеты для ссылки к памяти, которую вы хотите изменить, зная их месторасположение или сигнатуры. Как и для функций, необходимо создать файл. Этот файл содержит одно или несколько описаний ячейки памяти, к которой необходимо обратиться, или ее месторасположения.
Пример изменения стоимости AWP:

[ 
    { 
        "name" : "awpCost", 
        "library" : "mod", 
        "type" : "long", 
        "memoryType" : "data", 
        "identifiers" :  
        [ 
            { 
                "os" : "windows", 
                "mod" : "cstrike", 
                "value" : [0x8E,0x12,0x00,0x00,0x7D,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x07] 
            } 
        ] 
    } 
] 

OrpheuMemorySet(“awpCost”,1,1000)

Это всего лишь пример для “windows” и “cstrike”, однако сам модуль может быть использован и для Windows, и для Linux, а также для любого мода. Большую информацию читайте по ссылкам ниже.

Примечание: если необходимо заменить строки, напрямую размещенные в памяти (таких большинство), вам следует использовать тип “string” вместо “char *”.

Ссылки.
http://jsoncpp.sourceforge.net/
http://www.boost.org/
http://forums.alliedmods.net/showpost.php?p=994447&postcount=2

Перевод: Dani Minch
PS статья переводилась около 3-4 часов подряд и потом почти не изменялась, могут быть загоны) если что пишите в комментариях

Статьи по теме:
Orpheu: Поиск функций в библиотеках
Сигнатуры функций.

Sorry, the comment form is closed at this time.