reorder folders
This commit is contained in:
390
src/metamod/engineinfo.cpp
Normal file
390
src/metamod/engineinfo.cpp
Normal file
@@ -0,0 +1,390 @@
|
||||
|
||||
// vi: set ts=4 sw=4 :
|
||||
// vim: set tw=75 :
|
||||
|
||||
// engineinfo.cpp - get info about HL engine, like file used
|
||||
// and test segment range
|
||||
//
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// Don't include winspool.h; clashes with SERVER_EXECUTE from engine
|
||||
# define _WINSPOOL_H
|
||||
# include <windows.h>
|
||||
# include <winnt.h> // Header structures
|
||||
#else
|
||||
# ifndef _GNU_SOURCE
|
||||
# define _GNU_SOURCE
|
||||
# endif
|
||||
# include <dlfcn.h> // dladdr()
|
||||
# include <link.h> // ElfW(Phdr/Ehdr) macros.
|
||||
// _DYNAMIC, r_debug, link_map, etc.
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#include <string.h> // strlen(), strrchr(), strcmp()
|
||||
#include <stdio.h> // printf()
|
||||
|
||||
#include "engineinfo.h" // me
|
||||
#include "log_meta.h" // META_DEV()
|
||||
|
||||
|
||||
// Current mask for checking engine function addresses
|
||||
// in VAC protected engine dlls on Win32. This is gonna fail
|
||||
// on Win64.
|
||||
const unsigned long c_VacDllEngineFuncsRangeMask = 0xFFF00000;
|
||||
const unsigned long c_VacDllEngineFuncsRangeMark = 0x01D00000;
|
||||
void* const c_VacDllEngineFuncsRangeStart = (void*)0x01D00000;
|
||||
void* const c_VacDllEngineFuncsRangeEnd = (void*)0x01E00000;
|
||||
|
||||
|
||||
bool DLLINTERNAL EngineInfo::check_for_engine_module( const char* _pName )
|
||||
{
|
||||
const char* pC;
|
||||
size_t size;
|
||||
|
||||
if ( NULL == _pName ) return false;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// The engine module is either sw.dll or hw.dll or swds.dll
|
||||
pC = strrchr( _pName, '.' );
|
||||
|
||||
pC -= 2;
|
||||
if ( 0 != strcmp(pC, "sw.dll") && 0 != strcmp(pC, "hw.dll") ) {
|
||||
pC -= 2;
|
||||
if ( 0 != strcmp(pC, "swds.dll") ) return false;
|
||||
}
|
||||
|
||||
HMODULE hModule = GetModuleHandle( pC );
|
||||
if ( NULL == hModule ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ok, we found the string sw(ds).dll, thus we deduct that this is the
|
||||
// name of the engine's shared object. We copy the string for future
|
||||
// reference and return successfully.
|
||||
size = 0;
|
||||
while ( *pC != '.' && size < c_EngineInfo__typeLen-1 ) {
|
||||
m_type[size++] = *pC++;
|
||||
}
|
||||
m_type[size] = '\0';
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
const char* pType;
|
||||
|
||||
|
||||
size = strlen( _pName );
|
||||
if ( size < 11 ) {
|
||||
// Forget it, this string is too short to even be 'engine_.so', so
|
||||
// it can't the name of the engine shared library.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start parsing at the end. Since we know that the string is at least
|
||||
// 11 characters long we can safely do our parsing without being afraid
|
||||
// of leaving the string.
|
||||
pC = _pName + size -1;
|
||||
|
||||
// First we see if the string ends in '.so'
|
||||
if ( *pC-- != 'o' || *pC-- != 's' || *pC-- != '.' ) return false;
|
||||
|
||||
// Now find the '_' which would be the seperator between 'engine' and
|
||||
// the architecture.
|
||||
while ( *pC != '_' && pC != _pName ) --pC;
|
||||
if ( pC == _pName ) return false;
|
||||
|
||||
// We are at the '_', thus the architecture identifier must start at
|
||||
// the next character.
|
||||
pType = pC +1;
|
||||
|
||||
// Now we walk further back and check if we find the string 'engine'
|
||||
// backwards.
|
||||
--pC;
|
||||
if ( *pC-- != 'e' || *pC-- != 'n' || *pC-- != 'i' ||
|
||||
*pC-- != 'g' || *pC-- != 'n' || *pC != 'e' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ok, we found the string engine_*.so, thus we deduct that this is the
|
||||
// name of the engine's shared object. We copy the architecture string
|
||||
// for future reference and return successfully.
|
||||
size = 0;
|
||||
while ( *pType != '.' && size < c_EngineInfo__typeLen-1 ) {
|
||||
m_type[size++] = *pType++;
|
||||
}
|
||||
m_type[size] = '\0';
|
||||
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int DLLINTERNAL EngineInfo::nthdr_module_name( void )
|
||||
{
|
||||
PIMAGE_DOS_HEADER pDosHeader;
|
||||
PIMAGE_NT_HEADERS pNTHeader;
|
||||
unsigned char* pBaseAddr;
|
||||
const char* pName = "sw.dll";
|
||||
|
||||
if ( ! check_for_engine_module(pName) ) {
|
||||
pName = "hw.dll";
|
||||
if ( ! check_for_engine_module(pName) ) {
|
||||
pName = "swds.dll";
|
||||
if ( ! check_for_engine_module(pName) ) {
|
||||
return MODULE_NAME_NOTFOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the module handle for the engine dll we found.
|
||||
// This is also the modules base address.
|
||||
HMODULE hModule = GetModuleHandle( pName );
|
||||
|
||||
pBaseAddr = (unsigned char*)hModule;
|
||||
|
||||
|
||||
// Check if we find a DOS header
|
||||
pDosHeader = (PIMAGE_DOS_HEADER)hModule;
|
||||
if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) {
|
||||
return INVALID_DOS_SIGN;
|
||||
}
|
||||
|
||||
// Check if we find a PE header
|
||||
pNTHeader = (PIMAGE_NT_HEADERS)(pBaseAddr + pDosHeader->e_lfanew);
|
||||
if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) {
|
||||
return INVALID_NT_SIGN;
|
||||
}
|
||||
|
||||
set_code_range( pBaseAddr, pNTHeader );
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int DLLINTERNAL EngineInfo::vac_pe_approx( enginefuncs_t* _pFuncs )
|
||||
{
|
||||
if ( NULL == _pFuncs ) return INVALID_ARG;
|
||||
|
||||
// There is really no good and easy way to do this. Right now what
|
||||
// we do is assume that Steam listenservers will normally be
|
||||
// up-to-date and hence have all function pointers set correctly.
|
||||
// So we just check for some anomality and then set some approximated
|
||||
// values.
|
||||
|
||||
// The known addresses at the time of writing all start with 0x01D and
|
||||
// this lie in the range between 0x01D00000 and 0x01E00000. What we do
|
||||
// is check all functions pointers that are known to be good to start
|
||||
// with 0x01D. If that is the case then we know that the loading address
|
||||
// of the engine functions hasn't changed and we assume the above stated
|
||||
// range to be the range of valid engine function addresses.
|
||||
// If we find functions outside this range we can't determine a valid
|
||||
// range and have to just give up.
|
||||
|
||||
unsigned long* pengfuncs = (unsigned long*)_pFuncs;
|
||||
unsigned int i, invals = 0;
|
||||
for ( i = 0, pengfuncs += i; i < 140; i++, pengfuncs++ ) {
|
||||
if ( ((*pengfuncs) & c_VacDllEngineFuncsRangeMask) != c_VacDllEngineFuncsRangeMark ) {
|
||||
invals++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( invals > 0 ) {
|
||||
META_DEV( "Found %d engine functions out of range 0x%08lx. "
|
||||
"Unable to determine valid engine code address range.",
|
||||
invals, c_VacDllEngineFuncsRangeMark );
|
||||
|
||||
strncpy( m_type, "vacdll+?", c_EngineInfo__typeLen );
|
||||
m_state = STATE_INVALID;
|
||||
return STATE_INVALID;
|
||||
}
|
||||
|
||||
m_codeStart = c_VacDllEngineFuncsRangeStart;
|
||||
m_codeEnd = c_VacDllEngineFuncsRangeEnd;
|
||||
|
||||
strncpy( m_type, "vacdll", c_EngineInfo__typeLen );
|
||||
|
||||
m_state = STATE_VALID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void DLLINTERNAL EngineInfo::set_code_range( unsigned char* _pBase, PIMAGE_NT_HEADERS _pNThdr )
|
||||
{
|
||||
m_codeStart = _pBase + _pNThdr->OptionalHeader.BaseOfCode;
|
||||
m_codeEnd = _pBase + _pNThdr->OptionalHeader.BaseOfCode + _pNThdr->OptionalHeader.SizeOfCode;
|
||||
|
||||
m_state = STATE_VALID;
|
||||
}
|
||||
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
|
||||
int DLLINTERNAL EngineInfo::phdr_elfhdr( void* _pElfHdr )
|
||||
{
|
||||
ElfW(Ehdr)* pEhdr = (ElfW(Ehdr)*)_pElfHdr;
|
||||
ElfW(Phdr)* pPhdr;
|
||||
|
||||
if ( NULL == _pElfHdr ) return INVALID_ARG;
|
||||
|
||||
if ( pEhdr->e_ident[0] == 0x7f
|
||||
&& pEhdr->e_ident[1] == 'E'
|
||||
&& pEhdr->e_ident[2] == 'L'
|
||||
&& pEhdr->e_ident[3] == 'F' ) {
|
||||
|
||||
// Looking good, we found an ELF signature.
|
||||
// Let's see if the rest of the data at this address would fit an ELH header, too.
|
||||
if ( (pEhdr->e_ident[EI_CLASS] == ELFCLASS32 //ELFW(CLASS)
|
||||
|| pEhdr->e_ident[EI_CLASS] == ELFCLASS64)
|
||||
|
||||
&& (pEhdr->e_ident[EI_DATA] == ELFDATA2LSB
|
||||
|| pEhdr->e_ident[EI_DATA] == ELFDATA2MSB)
|
||||
|
||||
&& pEhdr->e_type == ET_DYN ) {
|
||||
|
||||
// Ok, we believe that this is a shared object's ELF header.
|
||||
// Let's find the program header for the segment that includes
|
||||
// the text section and return it.
|
||||
|
||||
pPhdr = (ElfW(Phdr) *) ((char *) pEhdr + pEhdr->e_phoff);
|
||||
for ( int i = 0; i < pEhdr->e_phnum; i++, pPhdr++ ) {
|
||||
|
||||
if ( PT_LOAD == pPhdr->p_type
|
||||
&& (pPhdr->p_flags & PF_R)
|
||||
&& (pPhdr->p_flags & PF_X) ) {
|
||||
|
||||
// The text section gets loaded and has read and
|
||||
// execute flags set. So this must be it.
|
||||
set_code_range( pEhdr, pPhdr );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HEADER_NOTFOUND;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int DLLINTERNAL EngineInfo::phdr_dladdr( void* _pMem )
|
||||
{
|
||||
Dl_info info;
|
||||
|
||||
if ( 0 != dladdr(_pMem, &info) ) {
|
||||
// Check if this is the engine module
|
||||
if ( check_for_engine_module(info.dli_fname) ) {
|
||||
return phdr_elfhdr( info.dli_fbase );
|
||||
}
|
||||
}
|
||||
|
||||
return NOTFOUND;
|
||||
}
|
||||
|
||||
|
||||
int DLLINTERNAL EngineInfo::phdr_r_debug( void )
|
||||
{
|
||||
ElfW(Dyn)* pDyn;
|
||||
struct r_debug* pr_debug;
|
||||
struct link_map* pMap;
|
||||
|
||||
|
||||
// Search if we have a DT_DEBUG symbol in our DYNAMIC segment, which
|
||||
// ought to be set to the r_debug structure's address.
|
||||
for (pDyn = _DYNAMIC; pDyn->d_tag != DT_NULL; ++pDyn) {
|
||||
if (pDyn->d_tag == DT_DEBUG) {
|
||||
pr_debug = (struct r_debug *) pDyn->d_un.d_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( DT_NULL == pDyn->d_tag ) {
|
||||
}
|
||||
else if ( NULL == pr_debug ) {
|
||||
}
|
||||
else {
|
||||
pMap = pr_debug->r_map;
|
||||
|
||||
// Walk to the start of the list
|
||||
while ( pMap->l_prev != NULL ) pMap = pMap->l_prev;
|
||||
do {
|
||||
if ( check_for_engine_module(pMap->l_name) ) {
|
||||
return phdr_elfhdr( (void*)pMap->l_addr );
|
||||
}
|
||||
|
||||
pMap = pMap->l_next;
|
||||
} while ( NULL != pMap );
|
||||
|
||||
}
|
||||
|
||||
return NOTFOUND;
|
||||
}
|
||||
|
||||
void DLLINTERNAL EngineInfo::set_code_range( void* _pBase, ElfW(Phdr)* _pPhdr )
|
||||
{
|
||||
unsigned char* pBase = (unsigned char*)_pBase;
|
||||
m_codeStart = pBase + _pPhdr->p_vaddr;
|
||||
m_codeEnd = pBase + _pPhdr->p_vaddr + _pPhdr->p_memsz;
|
||||
|
||||
m_state = STATE_VALID;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
|
||||
int DLLINTERNAL EngineInfo::initialise( enginefuncs_t* _pFuncs )
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
// Have to do this only once.
|
||||
if ( NULL != m_codeStart ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
ret = nthdr_module_name();
|
||||
if ( MODULE_NAME_NOTFOUND == ret && (! _pFuncs->pfnIsDedicatedServer()) ) {
|
||||
// We could not find the engine dll by name and we are running on
|
||||
// a listen server. This usually means that that we are dealing with
|
||||
// a VAC protected engine dll. No other way than approximating the
|
||||
// range of valid engine function pointers in this case.
|
||||
ret = vac_pe_approx( _pFuncs );
|
||||
}
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
// If we have no reference pointer to start from we can only try to use
|
||||
// the r_debug symbol.
|
||||
if ( NULL == _pFuncs ) {
|
||||
ret = phdr_r_debug();
|
||||
}
|
||||
|
||||
// If we have a refererence pointer we try to use it first.
|
||||
if ( 0 != phdr_dladdr(_pFuncs) ) {
|
||||
ret = phdr_r_debug();
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if ( 0 != ret ) {
|
||||
META_DEV( "Unable to determine engine code address range!" );
|
||||
}
|
||||
else {
|
||||
META_DEV( "Set engine code range: start address = %p, end address = %p",
|
||||
m_codeStart, m_codeEnd );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user