diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c new file mode 100644 index 00000000..f4dfe4aa --- /dev/null +++ b/engine/platform/win32/lib_custom_win.c @@ -0,0 +1,478 @@ +/* +lib_custom_win.c - win32 custom dlls loader +Copyright (C) 2008 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "common.h" + +#if XASH_LIB == LIB_WIN32 && XASH_X86 +#include "lib_win.h" + +#define NUMBER_OF_DIRECTORY_ENTRIES 16 +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) +#endif + +typedef struct +{ + PIMAGE_NT_HEADERS headers; + byte *codeBase; + void **modules; + int numModules; + int initialized; +} MEMORYMODULE, *PMEMORYMODULE; + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = +{ +{ +{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable +{ PAGE_READONLY, PAGE_READWRITE }, +}, +{ +{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable +{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, +}, +}; + +typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); + +#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] + +static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + byte *codeBase = module->codeBase; + int i, size; + byte *dest; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + + if( size > 0 ) + { + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); + section->Misc.PhysicalAddress = (DWORD)dest; + memset( dest, 0, size ); + } + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); + memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + byte *codeBase = module->codeBase; + int i, size; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + size = old_headers->OptionalHeader.SectionAlignment; + if( size > 0 ) + { + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } + continue; + } + + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } +} + +static void FinalizeSections( MEMORYMODULE *module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + int i; + + // loop through all sections and change access flags + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + + if( size == 0 ) + { + if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if( size > 0 ) + { + // change memory access flags + if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) + Sys_Error( "error protecting memory page\n" ); + } + } +} + +static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); + byte *codeBase = module->codeBase; + DWORD i; + + if( directory->Size > 0 ) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + for( ; relocation->VirtualAddress > 0; ) + { + byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); + word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); + + for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch( type ) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); + *patchAddrHL += delta; + break; + default: + Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); + } + } +} + +FARPROC MemoryGetProcAddress( void *module, const char *name ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); + byte *codeBase = ((PMEMORYMODULE)module)->codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + int idx = -1; + DWORD i, *nameRef; + WORD *ordinal; + + if( directory->Size == 0 ) + { + // no export table found + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) + { + // DLL doesn't export anything + return NULL; + } + + // search function name in list of exported names + nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); + ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); + + for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) + { + // GetProcAddress case insensative ????? + if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) + { + idx = *ordinal; + break; + } + } + + if( idx == -1 ) + { + // exported symbol not found + return NULL; + } + + if((DWORD)idx > exports->NumberOfFunctions ) + { + // name <-> ordinal number don't match + return NULL; + } + + // addressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); +} + +static int BuildImportTable( MEMORYMODULE *module ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); + byte *codeBase = module->codeBase; + int result = 1; + + if( directory->Size > 0 ) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) + { + DWORD *thunkRef, *funcRef; + LPCSTR libname; + void *handle; + + libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); + handle = COM_LoadLibrary( libname, false, true ); + + if( handle == NULL ) + { + Con_Printf( S_ERROR "couldn't load library %s\n", libname ); + result = 0; + break; + } + + module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); + module->modules[module->numModules++] = handle; + + if( importDesc->OriginalFirstThunk ) + { + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + else + { + // no hint table + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + + for( ; *thunkRef; thunkRef++, funcRef++ ) + { + LPCSTR funcName; + + if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) + { + funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + else + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); + funcName = (LPCSTR)&thunkData->Name; + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + + if( *funcRef == 0 ) + { + Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); + result = 0; + break; + } + } + if( !result ) break; + } + } + return result; +} + +void MemoryFreeLibrary( void *hInstance ) +{ + MEMORYMODULE *module = (MEMORYMODULE *)hInstance; + + if( module != NULL ) + { + int i; + + if( module->initialized != 0 ) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); + module->initialized = 0; + } + + if( module->modules != NULL ) + { + // free previously opened libraries + for( i = 0; i < module->numModules; i++ ) + { + if( module->modules[i] != NULL ) + COM_FreeLibrary( module->modules[i] ); + } + Mem_Free( module->modules ); // Mem_Realloc end + } + + FreeSections( module->headers, module ); + + if( module->codeBase != NULL ) + { + // release memory of library + VirtualFree( module->codeBase, 0, MEM_RELEASE ); + } + + HeapFree( GetProcessHeap(), 0, module ); + } +} + +void *MemoryLoadLibrary( const char *name ) +{ + MEMORYMODULE *result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + byte *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + string errorstring; + qboolean successfull; + void *data = NULL; + + data = FS_LoadFile( name, NULL, false ); + + if( !data ) + { + Q_sprintf( errorstring, "couldn't load %s", name ); + goto library_error; + } + + dos_header = (PIMAGE_DOS_HEADER)data; + if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + goto library_error; + } + + old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; + if( old_header->Signature != IMAGE_NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s missing PE header", name ); + goto library_error; + } + + // reserve memory for image of library + code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + + if( code == NULL ) + { + // try to allocate memory at arbitrary position + code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + } + + if( code == NULL ) + { + Q_sprintf( errorstring, "%s can't reserve memory", name ); + goto library_error; + } + + result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); + + // commit memory for headers + headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); + + // copy PE header to code + memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); + result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections( data, old_header, result ); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); + + // load required dlls and adjust function table of imports + if( !BuildImportTable( result )) + { + Q_sprintf( errorstring, "%s failed to build import table", name ); + goto library_error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections( result ); + + // get entry point of loaded library + if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) + { + DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); + if( DllEntry == 0 ) + { + Q_sprintf( errorstring, "%s has no entry point", name ); + goto library_error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); + if( !successfull ) + { + Q_sprintf( errorstring, "can't attach library %s", name ); + goto library_error; + } + result->initialized = 1; + } + + Mem_Free( data ); // release memory + return (void *)result; +library_error: + // cleanup + if( data ) Mem_Free( data ); + MemoryFreeLibrary( result ); + Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + + return NULL; +} + +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index b5ee3e76..f5133b58 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -1,5 +1,5 @@ /* -library.c - custom dlls loader +lin_win.c - win32 dynamic library loading Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -12,481 +12,11 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "platform/platform.h" -#if XASH_LIB == LIB_WIN32 -#include "common.h" -#include "library.h" -#include - -#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) - -#if XASH_X86 -/* ---------------------------------------------------------------- - - Custom dlls loader - ---------------------------------------------------------------- -*/ - -#define NUMBER_OF_DIRECTORY_ENTRIES 16 -#ifndef IMAGE_SIZEOF_BASE_RELOCATION -#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) -#endif - -typedef struct -{ - PIMAGE_NT_HEADERS headers; - byte *codeBase; - void **modules; - int numModules; - int initialized; -} MEMORYMODULE, *PMEMORYMODULE; - -// Protection flags for memory pages (Executable, Readable, Writeable) -static int ProtectionFlags[2][2][2] = -{ -{ -{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable -{ PAGE_READONLY, PAGE_READWRITE }, -}, -{ -{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable -{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, -}, -}; - -typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); - -#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] - -static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - byte *codeBase = module->codeBase; - int i, size; - byte *dest; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - // section doesn't contain data in the dll itself, but may define - // uninitialized data - size = old_headers->OptionalHeader.SectionAlignment; - - if( size > 0 ) - { - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); - section->Misc.PhysicalAddress = (DWORD)dest; - memset( dest, 0, size ); - } - // section is empty - continue; - } - - // commit memory block and copy data from dll - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); - memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); - section->Misc.PhysicalAddress = (DWORD)dest; - } -} - -static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); - byte *codeBase = module->codeBase; - int i, size; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - size = old_headers->OptionalHeader.SectionAlignment; - if( size > 0 ) - { - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } - continue; - } - - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } -} - -static void FinalizeSections( MEMORYMODULE *module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - int i; - - // loop through all sections and change access flags - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - DWORD protect, oldProtect, size; - int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; - int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; - - if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) - { - // section is not needed any more and can safely be freed - VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); - continue; - } - - // determine protection flags based on characteristics - protect = ProtectionFlags[executable][readable][writeable]; - if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) - protect |= PAGE_NOCACHE; - - // determine size of region - size = section->SizeOfRawData; - - if( size == 0 ) - { - if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfInitializedData; - else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfUninitializedData; - } - - if( size > 0 ) - { - // change memory access flags - if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) - Sys_Error( "error protecting memory page\n" ); - } - } -} - -static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); - byte *codeBase = module->codeBase; - DWORD i; - - if( directory->Size > 0 ) - { - PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - for( ; relocation->VirtualAddress > 0; ) - { - byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); - word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); - - for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) - { - DWORD *patchAddrHL; - int type, offset; - - // the upper 4 bits define the type of relocation - type = *relInfo >> 12; - // the lower 12 bits define the offset - offset = *relInfo & 0xfff; - - switch( type ) - { - case IMAGE_REL_BASED_ABSOLUTE: - // skip relocation - break; - case IMAGE_REL_BASED_HIGHLOW: - // change complete 32 bit address - patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); - *patchAddrHL += delta; - break; - default: - Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); - break; - } - } - - // advance to next relocation block - relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); - } - } -} - -static FARPROC MemoryGetProcAddress( void *module, const char *name ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); - byte *codeBase = ((PMEMORYMODULE)module)->codeBase; - PIMAGE_EXPORT_DIRECTORY exports; - int idx = -1; - DWORD i, *nameRef; - WORD *ordinal; - - if( directory->Size == 0 ) - { - // no export table found - return NULL; - } - - exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) - { - // DLL doesn't export anything - return NULL; - } - - // search function name in list of exported names - nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); - ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); - - for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) - { - // GetProcAddress case insensative ????? - if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) - { - idx = *ordinal; - break; - } - } - - if( idx == -1 ) - { - // exported symbol not found - return NULL; - } - - if((DWORD)idx > exports->NumberOfFunctions ) - { - // name <-> ordinal number don't match - return NULL; - } - - // addressOfFunctions contains the RVAs to the "real" functions - return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); -} - -static int BuildImportTable( MEMORYMODULE *module ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); - byte *codeBase = module->codeBase; - int result = 1; - - if( directory->Size > 0 ) - { - PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) - { - DWORD *thunkRef, *funcRef; - LPCSTR libname; - void *handle; - - libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); - handle = COM_LoadLibrary( libname, false, true ); - - if( handle == NULL ) - { - Con_Printf( S_ERROR "couldn't load library %s\n", libname ); - result = 0; - break; - } - - module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); - module->modules[module->numModules++] = handle; - - if( importDesc->OriginalFirstThunk ) - { - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - else - { - // no hint table - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - - for( ; *thunkRef; thunkRef++, funcRef++ ) - { - LPCSTR funcName; - - if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) - { - funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - else - { - PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); - funcName = (LPCSTR)&thunkData->Name; - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - - if( *funcRef == 0 ) - { - Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); - result = 0; - break; - } - } - if( !result ) break; - } - } - return result; -} - -static void MemoryFreeLibrary( void *hInstance ) -{ - MEMORYMODULE *module = (MEMORYMODULE *)hInstance; - - if( module != NULL ) - { - int i; - - if( module->initialized != 0 ) - { - // notify library about detaching from process - DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); - (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); - module->initialized = 0; - } - - if( module->modules != NULL ) - { - // free previously opened libraries - for( i = 0; i < module->numModules; i++ ) - { - if( module->modules[i] != NULL ) - COM_FreeLibrary( module->modules[i] ); - } - Mem_Free( module->modules ); // Mem_Realloc end - } - - FreeSections( module->headers, module ); - - if( module->codeBase != NULL ) - { - // release memory of library - VirtualFree( module->codeBase, 0, MEM_RELEASE ); - } - - HeapFree( GetProcessHeap(), 0, module ); - } -} -void *MemoryLoadLibrary( const char *name ) -{ - MEMORYMODULE *result = NULL; - PIMAGE_DOS_HEADER dos_header; - PIMAGE_NT_HEADERS old_header; - byte *code, *headers; - DWORD locationDelta; - DllEntryProc DllEntry; - string errorstring; - qboolean successfull; - void *data = NULL; - - data = FS_LoadFile( name, NULL, false ); - - if( !data ) - { - Q_sprintf( errorstring, "couldn't load %s", name ); - goto library_error; - } - - dos_header = (PIMAGE_DOS_HEADER)data; - if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) - { - Q_sprintf( errorstring, "%s it's not a valid executable file", name ); - goto library_error; - } - - old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; - if( old_header->Signature != IMAGE_NT_SIGNATURE ) - { - Q_sprintf( errorstring, "%s missing PE header", name ); - goto library_error; - } - - // reserve memory for image of library - code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - - if( code == NULL ) - { - // try to allocate memory at arbitrary position - code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - } - - if( code == NULL ) - { - Q_sprintf( errorstring, "%s can't reserve memory", name ); - goto library_error; - } - - result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); - result->codeBase = code; - result->numModules = 0; - result->modules = NULL; - result->initialized = 0; - - // XXX: is it correct to commit the complete memory region at once? - // calling DllEntry raises an exception if we don't... - VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); - - // commit memory for headers - headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); - - // copy PE header to code - memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); - result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; - - // update position - result->headers->OptionalHeader.ImageBase = (DWORD)code; - - // copy sections from DLL file block to new memory location - CopySections( data, old_header, result ); - - // adjust base address of imported data - locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); - if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); - - // load required dlls and adjust function table of imports - if( !BuildImportTable( result )) - { - Q_sprintf( errorstring, "%s failed to build import table", name ); - goto library_error; - } - - // mark memory pages depending on section headers and release - // sections that are marked as "discardable" - FinalizeSections( result ); - - // get entry point of loaded library - if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) - { - DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); - if( DllEntry == 0 ) - { - Q_sprintf( errorstring, "%s has no entry point", name ); - goto library_error; - } - - // notify library about attaching to process - successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); - if( !successfull ) - { - Q_sprintf( errorstring, "can't attach library %s", name ); - goto library_error; - } - result->initialized = 1; - } - - Mem_Free( data ); // release memory - return (void *)result; -library_error: - // cleanup - if( data ) Mem_Free( data ); - MemoryFreeLibrary( result ); - Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); +#include "common.h" - return NULL; -} -#endif +#if XASH_LIB == LIB_WIN32 +#include "lib_win.h" static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) { @@ -764,70 +294,146 @@ table_error: return false; } -qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +static const char *GetLastErrorAsString( void ) +{ + DWORD errorcode; + static string errormessage; + + errorcode = GetLastError(); + if ( !errorcode ) return ""; + + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, errorcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + (LPSTR)&errormessage, sizeof( errormessage ), NULL ); + + return errormessage; +} + +static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *data, PIMAGE_NT_HEADERS *peheader ) { - PIMAGE_DOS_HEADER dosHeader; - PIMAGE_NT_HEADERS peHeader; - PIMAGE_DATA_DIRECTORY importDir; + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_DATA_DIRECTORY importDir; PIMAGE_IMPORT_DESCRIPTOR importDesc; - string errorstring = ""; - byte *data = NULL; - dll_user_t *hInst; - hInst = FS_FindLibrary( name, directpath ); - if( !hInst ) + if ( !data ) { - return false; // nothing to load + Con_Printf( S_ERROR "%s: couldn't load %s\n", __FUNCTION__, name ); + return NULL; } - data = FS_LoadFile( name, NULL, false ); - if( !data ) + dosHeader = (PIMAGE_DOS_HEADER)data; + if ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __FUNCTION__, name ); + return NULL; } - dosHeader = ( PIMAGE_DOS_HEADER )data; - if( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) + peHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew ); + if ( peHeader->Signature != IMAGE_NT_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __FUNCTION__, name ); + return NULL; } - peHeader = ( PIMAGE_NT_HEADERS )(data + dosHeader->e_lfanew); - if( peHeader->Signature != IMAGE_NT_SIGNATURE ) + importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if( importDir->Size <= 0 ) { - Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s has no dependencies\n", __FUNCTION__, name ); + return NULL; } - importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; - if( importDir->Size <= 0 ) + *peheader = peHeader; + importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) ); + + return importDesc; +} + +static void ListMissingModules( dll_user_t *hInst ) +{ + DWORD cbNeeded; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + HANDLE hProcess; + byte *data; + + if ( !hInst ) return; + + hProcess = GetCurrentProcess(); + + data = FS_LoadFile( hInst->dllName, NULL, false ); + if ( !data ) + { + CloseHandle( hProcess ); + return; + } + + importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); + if ( !importDesc ) { - Con_Printf( S_WARN "%s: %s has no dependencies. Is this library valid?\n", __FUNCTION__, name ); - goto libraryerror; + CloseHandle( hProcess ); + Mem_Free( data ); + return; } - importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA(importDir->VirtualAddress, peHeader) ); - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++ ) + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) { + HMODULE hMod; const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); - Con_Reportf( "library %s has direct dependency %s\n", name, importName ); - if( !Q_stricmp( importName, depname ) ) + hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); + if ( !hMod ) + COM_PushLibraryError( va( "%s not found!", importName ) ); + else + FreeLibrary( hMod ); + } + + CloseHandle( hProcess ); + Mem_Free( data ); + return; +} + +qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + dll_user_t *hInst; + qboolean ret = FALSE; + + hInst = FS_FindLibrary( name, directpath ); + if ( !hInst ) return FALSE; + + data = FS_LoadFile( name, NULL, false ); + if ( !data ) + { + COM_FreeLibrary( hInst ); + return FALSE; + } + + importDesc = GetImportDescriptor( name, data, &peHeader ); + if ( !importDesc ) + { + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) + { + const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); + + if ( !Q_stricmp( importName, depname ) ) { + COM_FreeLibrary( hInst ); Mem_Free( data ); - return true; + return TRUE; } } -libraryerror: - if( errorstring[0] ) - { - Con_Printf( S_ERROR "%s: %s\n", __FUNCTION__, errorstring ); - } - if( data ) Mem_Free( data ); // release memory - return false; + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; } /* @@ -841,12 +447,19 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst; + COM_ResetLibraryError(); + hInst = FS_FindLibrary( dllname, directpath ); - if( !hInst ) return NULL; // nothing to load + if( !hInst ) + { + COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + return NULL; + } if( hInst->encrypted ) { - Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); + COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + COM_FreeLibrary( hInst ); return NULL; } @@ -863,7 +476,11 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( !hInst->hInstance ) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( GetLastErrorAsString() ); + + if ( GetLastError() == ERROR_MOD_NOT_FOUND ) + ListMissingModules( hInst ); + COM_FreeLibrary( hInst ); return NULL; } @@ -873,14 +490,12 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); COM_FreeLibrary( hInst ); return NULL; } } - Con_Reportf( "LoadLibrary: Loading %s - ok\n", dllname ); - return hInst; } @@ -944,7 +559,7 @@ void *COM_FunctionFromName( void *hInstance, const char *pName ) if( !Q_strcmp( pName, hInst->names[i] )) { index = hInst->ordinals[i]; - return hInst->funcs[index] + hInst->funcBase; + return (void *)( hInst->funcs[index] + hInst->funcBase ); } } diff --git a/engine/platform/win32/lib_win.h b/engine/platform/win32/lib_win.h new file mode 100644 index 00000000..429dd4f8 --- /dev/null +++ b/engine/platform/win32/lib_win.h @@ -0,0 +1,24 @@ +/* +lib_win.h - common win32 dll definitions +Copyright (C) 2022 Flying With Gauss + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "library.h" +#include +#include + +#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) + +FARPROC MemoryGetProcAddress( void *module, const char *name ); +void MemoryFreeLibrary( void *hInstance ); +void *MemoryLoadLibrary( const char *name ); \ No newline at end of file