diff --git a/metamod/CMakeLists.txt b/metamod/CMakeLists.txt index 8709a12..18d46fc 100644 --- a/metamod/CMakeLists.txt +++ b/metamod/CMakeLists.txt @@ -149,8 +149,7 @@ target_sources(metamod PRIVATE "src/engine_api.cpp" "src/game_support.cpp" "src/h_export.cpp" - "src/linkent.cpp" - "src/linkgame.cpp" + "src/osdep_linkent_linux.cpp" "src/log_meta.cpp" "src/mdebug.cpp" "src/mem_utils.cpp" @@ -174,6 +173,7 @@ target_sources(metamod PRIVATE target_link_libraries(metamod PRIVATE dl + pthread $<$:libc-2.11.1.so> ) diff --git a/metamod/msvc/metamod.vcxproj b/metamod/msvc/metamod.vcxproj index 40c66b2..853a509 100644 --- a/metamod/msvc/metamod.vcxproj +++ b/metamod/msvc/metamod.vcxproj @@ -151,8 +151,6 @@ - - @@ -164,6 +162,11 @@ + + true + true + + Create Create diff --git a/metamod/msvc/metamod.vcxproj.filters b/metamod/msvc/metamod.vcxproj.filters index 3d9ff5a..83793a9 100644 --- a/metamod/msvc/metamod.vcxproj.filters +++ b/metamod/msvc/metamod.vcxproj.filters @@ -35,12 +35,6 @@ src - - src - - - src - src @@ -98,6 +92,12 @@ src + + src + + + src + diff --git a/metamod/src/h_export.cpp b/metamod/src/h_export.cpp index 0d8e362..e381489 100644 --- a/metamod/src/h_export.cpp +++ b/metamod/src/h_export.cpp @@ -16,6 +16,7 @@ void WINAPI GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine, globalvars_t* pG g_engine.funcs = &g_engfuncs; g_engine.globals = pGlobals; g_engfuncs = *pengfuncsFromEngine; + g_metamod_module.load(&g_engfuncs); g_engine.sys_module.load(pengfuncsFromEngine); g_engfuncs = *pengfuncsFromEngine; diff --git a/metamod/src/linkent.cpp b/metamod/src/linkent.cpp deleted file mode 100644 index 20c124e..0000000 --- a/metamod/src/linkent.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "precompiled.h" - -// Function to perform common code of LINK_ENTITY_TO_GAME, rather than -// duplicating the code in ~2000 expanded macros. Based on code from Jussi -// Kivilinna . -void NOINLINE do_link_ent(ENTITY_FN* pfnEntity, int* missing, const char* entStr, entvars_t* pev) -{ - if (*missing) { - META_DEBUG(9, "Skipping entity '%s'; was previously found missing", entStr); - return; - } - - if (!*pfnEntity) { - META_DEBUG(9, "Looking up game entity '%s'", entStr); - *pfnEntity = (ENTITY_FN)g_GameDLL.sys_module.getsym(entStr); - } - - if (!*pfnEntity) { - META_ERROR("Couldn't find game entity '%s' in game DLL '%s': %s", entStr, g_GameDLL.name, CSysModule::getloaderror()); - *missing = 1; - return; - } - - META_DEBUG(8, "Linking game entity '%s'", entStr); - (*pfnEntity)(pev); -} diff --git a/metamod/src/linkent.h b/metamod/src/linkent.h index 7a16a75..76bc64c 100644 --- a/metamod/src/linkent.h +++ b/metamod/src/linkent.h @@ -1,19 +1,49 @@ +// vi: set ts=4 sw=4 : +// vim: set tw=75 : + +// linkent.h - export entities from mod "games" back to the HL engine + +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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 2 of the License, or (at + * your option) any later version. + * + * Metamod 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. + * + * You should have received a copy of the GNU General Public License + * along with Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + #pragma once +// Initializes replacement code +bool meta_init_linkent_replacement(CSysModule *moduleMetamod, CSysModule *moduleGame); + // Comments from SDK dlls/util.h: //! This is the glue that hooks .MAP entity class names to our CPP classes. //! The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress(). //! The function is used to intialize / allocate the object for the entity. // Adapted from LINK_ENTITY_TO_FUNC in adminmod linkfunc.cpp. -typedef void (*ENTITY_FN)(entvars_t *); -// Function to perform common code of LINK_ENTITY_TO_GAME. -void do_link_ent(ENTITY_FN* pfnEntity, int* missing, const char* entStr, entvars_t* pev); - -#define LINK_ENTITY_TO_GAME(entityName) \ - C_DLLEXPORT void entityName(entvars_t *pev) { \ - static ENTITY_FN pfnEntity = nullptr; \ - static int missing = 0; \ - do_link_ent(&pfnEntity, &missing, STRINGIZE(entityName, 0), pev); \ - } +typedef void (*ENTITY_FN) (entvars_t *); diff --git a/metamod/src/linkgame.cpp b/metamod/src/linkgame.cpp deleted file mode 100644 index 2360d20..0000000 --- a/metamod/src/linkgame.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "precompiled.h" - -// Entity list for gamedlls adapted from adminmod linkfunc.cpp. - -// NOTE: This list of entities is automatically generated via the script -// "mklinkgame.sh". See the files in the directory "ents" for the actual -// list of entities. - -LINK_ENTITY_TO_GAME(worldspawn); diff --git a/metamod/src/metamod.cpp b/metamod/src/metamod.cpp index 1fede1c..6d9d188 100644 --- a/metamod/src/metamod.cpp +++ b/metamod/src/metamod.cpp @@ -18,6 +18,8 @@ option_t g_global_options[] = gamedll_t g_GameDLL; +CSysModule g_metamod_module; + ALIGN16 meta_globals_t g_metaGlobals; @@ -229,10 +231,17 @@ void metamod_startup() // Exit on failure here? Dunno... } - meta_init_rehlds_api(); - - if (!g_meta_extdll.init(&g_engine.sys_module)) { - Sys_Error("Failure to init extension DLL; exiting..."); + if (meta_init_rehlds_api()) { + if (!g_meta_extdll.init(&g_engine.sys_module)) { + Sys_Error("Failure to init extension DLL; exiting..."); + } + } else { + // activate linkent-replacement after give_engfuncs so that if game dll is + // plugin too and uses same method we get combined export table of plugin + // and game dll + if (!meta_init_linkent_replacement(&g_metamod_module, &g_GameDLL.sys_module)) { + Sys_Error("dll: Couldn't load linkent replacement for game DLL"); + } } // Allow for commands to metamod plugins at startup. Autoexec.cfg is diff --git a/metamod/src/metamod.h b/metamod/src/metamod.h index fd770ef..49f755e 100644 --- a/metamod/src/metamod.h +++ b/metamod/src/metamod.h @@ -15,6 +15,9 @@ // cvar to contain version extern cvar_t g_meta_version; +// metamod module handle +extern CSysModule g_metamod_module; + // Info about the game dll/mod. struct gamedll_t { diff --git a/metamod/src/mextdll.cpp b/metamod/src/mextdll.cpp index 6331cbd..a51161b 100644 --- a/metamod/src/mextdll.cpp +++ b/metamod/src/mextdll.cpp @@ -3,7 +3,7 @@ CExtDll g_meta_extdll; CExtDll::CExtDll() - : m_hGameDLL(CSysModule::INVALID_HANDLE), m_dlls(nullptr), m_count(nullptr) + : m_hGameDLL(CSysModule::INVALID_HANDLE) { } @@ -17,10 +17,6 @@ void CExtDll::load() return; } - - auto extdll = &m_dlls[(*m_count)++]; - Q_memset(extdll, 0, sizeof(*extdll)); - extdll->lDLLHandle = m_hGameDLL; } void CExtDll::unload() @@ -29,68 +25,13 @@ void CExtDll::unload() g_RehldsFuncs->RemoveExtDll(m_hGameDLL); return; } - - for (auto i = 0u; i < *m_count; i++) - { - if (m_dlls[i].lDLLHandle == m_hGameDLL) - { - --(*m_count); - if (*m_count != i) - { - Q_memmove(&m_dlls[i], &m_dlls[i + 1], (*m_count - i) * sizeof(m_dlls[0])); - i = *m_count; - } - - Q_memset(&m_dlls[i], 0, sizeof(m_dlls[0])); - return; - } - } } bool CExtDll::init(CSysModule *module) { m_hGameDLL = g_GameDLL.sys_module.gethandle(); - - if (g_RehldsFuncs) { - return true; - } - - m_dlls = (extensiondll_t *)module->getsym("g_rgextdll"); - m_count = (size_t *)module->getsym("g_iextdllMac"); - - if (m_dlls && m_count) { - return true; - } - - // HACK HACK MORE HACK - // HLDS SHIT - auto pos = module->find_string_push("SV_SaveGameComment"); - if (!pos) { + if (!m_hGameDLL) return false; - } - const char patterns[2][2][14] = { - { "\x56\x33\xF6\x39\x2A\x2A\x2A\x2A\x2A\x7E\x2A\x53\x8B", "\x5B\xC3\x90\x90\xA1\x2A\x2A\x2A\x2A\x53\x56\x57\x33" }, - { "\x53\x8B\x2A\x2A\x2A\x2A\x2A\x57\xBF\x2A\x2A\x2A\x2A", "\x7E\x2A\x8B\x2A\x2A\x2A\x2A\x2A\xBE\x2A\x2A\x2A\x2A" }, - }; - - for (auto i = 0u; i < ARRAYSIZE(patterns); i++) - { - auto addr = mem_find_pattern(pos - 33, 20, patterns[0][i], sizeof(patterns[0][i]) - 1); - if (!addr) { - continue; - } - - m_count = *(size_t **)(addr + 5); - - addr = mem_find_pattern(addr, 30, patterns[1][i], sizeof(patterns[1][i]) - 1); - if (!addr) { - return false; - } - - m_dlls = *(extensiondll_t **)(addr + 9); - return true; - } - - return false; + return true; } diff --git a/metamod/src/mextdll.h b/metamod/src/mextdll.h index c1b561a..5ccc9fb 100644 --- a/metamod/src/mextdll.h +++ b/metamod/src/mextdll.h @@ -12,8 +12,6 @@ public: private: module_handle_t m_hGameDLL; - extensiondll_t *m_dlls; - size_t *m_count; }; extern CExtDll g_meta_extdll; diff --git a/metamod/src/osdep_linkent_linux.cpp b/metamod/src/osdep_linkent_linux.cpp new file mode 100644 index 0000000..76dbadc --- /dev/null +++ b/metamod/src/osdep_linkent_linux.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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 2 of the License, or (at + * your option) any later version. + * + * Metamod 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. + * + * You should have received a copy of the GNU General Public License + * along with Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include "precompiled.h" + +#define PAGE_SIZE 4096UL +#define PAGE_MASK (~(PAGE_SIZE - 1)) +#define PAGE_ALIGN(addr) (((addr) + PAGE_SIZE - 1) & PAGE_MASK) + + // + // Linux code for dynamic linkents + // -- by Jussi Kivilinna + // + + // opcode, e9, + sizeof pointer +#define BYTES_SIZE (1 + sizeof(void*)) + +typedef void *(*dlsym_func)(void *module, const char *funcname); + +static void *gamedll_module_handle = 0; +static void *metamod_module_handle = 0; + +// pointer to original dlsym +static dlsym_func dlsym_original; + +// contains jmp to replacement_dlsym @dlsym_original +static unsigned char dlsym_new_bytes[BYTES_SIZE]; + +// contains original bytes of dlsym +static unsigned char dlsym_old_bytes[BYTES_SIZE]; + +// Mutex for our protection +static pthread_mutex_t mutex_replacement_dlsym = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +// constructs new jmp forwarder +inline void construct_jmp_instruction(void *x, void *place, void *target) +{ + ((unsigned char *)x)[0] = 0xe9; + *(unsigned long *)((char *)x + 1) = (unsigned long)target - ((unsigned long)place + 5); +} + +// checks if pointer x points to jump forwarder +inline bool is_code_trampoline_jmp_opcode(void *x) +{ + return (((unsigned char *)x)[0] == 0xff || ((unsigned char *)x)[1] == 0x25); +} + +// extracts pointer from "jmp dword ptr[pointer]" +inline void *extract_function_pointer_from_trampoline_jmp(void *x) +{ + return (**(void ***)((char *)(x) + 2)); +} + +// +// restores old dlsym +// +inline void restore_original_dlsym() +{ + //Copy old dlsym bytes back + memcpy((void *)dlsym_original, dlsym_old_bytes, BYTES_SIZE); +} + +// +// resets new dlsym +// +inline void reset_dlsym_hook() +{ + //Copy new dlsym bytes back + memcpy((void *)dlsym_original, dlsym_new_bytes, BYTES_SIZE); +} + +// +// Replacement dlsym function +// +static void *__replacement_dlsym(void *module, const char *funcname) +{ + // these are needed in case dlsym calls dlsym, default one doesn't do + // it but some LD_PRELOADed library that hooks dlsym might actually + // do so. + static int is_original_restored = 0; + int was_original_restored = is_original_restored; + + // Lock before modifing original dlsym + pthread_mutex_lock(&mutex_replacement_dlsym); + + // restore old dlsym + if (!is_original_restored) + { + restore_original_dlsym(); + is_original_restored = 1; + } + + // check if we should hook this call + if (module != metamod_module_handle || !metamod_module_handle || !gamedll_module_handle) + { + //no metamod/gamedll module? should we remove hook now? + void *retval = dlsym_original(module, funcname); + if (metamod_module_handle && gamedll_module_handle) + { + if (!was_original_restored) + { + // reset dlsym hook + reset_dlsym_hook(); + is_original_restored = 0; + } + } + else + { + // no metamod/gamedll module? should we remove hook now by not reseting it back? + } + + // unlock + pthread_mutex_unlock(&mutex_replacement_dlsym); + return retval; + } + + // dlsym on metamod module + void *func = dlsym_original(module, funcname); + + if (!func) + { + // function not in metamod module, try gamedll + func = dlsym_original(gamedll_module_handle, funcname); + } + + if (!was_original_restored) + { + // reset dlsym hook + reset_dlsym_hook(); + + is_original_restored = 0; + } + + // unlock + pthread_mutex_unlock(&mutex_replacement_dlsym); + return func; +} + +// +// Initialize +// +bool meta_init_linkent_replacement(CSysModule *moduleMetamod, CSysModule *moduleGame) +{ + metamod_module_handle = moduleMetamod->gethandle(); + gamedll_module_handle = moduleGame->gethandle(); + + // dlsym is already known to be pointing to valid function, we loaded gamedll using it earlier! + void *sym_ptr = (void *)&dlsym; + while (is_code_trampoline_jmp_opcode(sym_ptr)) + { + sym_ptr = extract_function_pointer_from_trampoline_jmp(sym_ptr); + } + + dlsym_original = (dlsym_func)sym_ptr; + + // Backup old bytes of "dlsym" function + memcpy(dlsym_old_bytes, (void *)dlsym_original, BYTES_SIZE); + + // Construct new bytes: "jmp offset[replacement_sendto] @ sendto_original" + construct_jmp_instruction((void *)&dlsym_new_bytes[0], (void *)dlsym_original, (void *)&__replacement_dlsym); + + // Check if bytes overlap page border. + unsigned long start_of_page = PAGE_ALIGN((long)dlsym_original) - PAGE_SIZE; + unsigned long size_of_pages = 0; + + if ((unsigned long)dlsym_original + BYTES_SIZE > PAGE_ALIGN((unsigned long)dlsym_original)) + { + // bytes are located on two pages + size_of_pages = PAGE_SIZE * 2; + } + else + { + // bytes are located entirely on one page. + size_of_pages = PAGE_SIZE; + } + + // Remove PROT_READ restriction + if (mprotect((void *)start_of_page, size_of_pages, PROT_READ | PROT_WRITE | PROT_EXEC)) + { + META_ERROR("Couldn't initialize dynamic linkents, mprotect failed: %i. Exiting...", errno); + return false; + } + + // Write our own jmp-forwarder on "dlsym" + reset_dlsym_hook(); + + // done + return true; +} diff --git a/metamod/src/osdep_linkent_win32.cpp b/metamod/src/osdep_linkent_win32.cpp new file mode 100644 index 0000000..fa9f52a --- /dev/null +++ b/metamod/src/osdep_linkent_win32.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2004-2006 Jussi Kivilinna + * + * This file is part of "Metamod All-Mod-Support"-patch for Metamod. + * + * Metamod 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 2 of the License, or (at + * your option) any later version. + * + * Metamod 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. + * + * You should have received a copy of the GNU General Public License + * along with Metamod; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * In addition, as a special exception, the author gives permission to + * link the code of this program with the Half-Life Game Engine ("HL + * Engine") and Modified Game Libraries ("MODs") developed by Valve, + * L.L.C ("Valve"). You must obey the GNU General Public License in all + * respects for all of the code used other than the HL Engine and MODs + * from Valve. If you modify this file, you may extend this exception + * to your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. + * + */ + +#include "precompiled.h" + +// +// Win32 code for dynamic linkents +// -- by Jussi Kivilinna +// + +// +// Reads metamod.dll and game.dll function export tables and combines theim to +// single table that replaces metamod.dll's original table. +// + +typedef struct sort_names_s { + unsigned long name; + unsigned short nameOrdinal; +} sort_names_t; + +// relative virtual address to virtual address +#define rva_to_va(base, rva) ((unsigned long)base + (unsigned long)rva) + +// virtual address to relative virtual address +#define va_to_rva(base, va) ((unsigned long)va - (unsigned long)base) + +// +// Checks module signatures and return ntheaders pointer for valid module +// +static IMAGE_NT_HEADERS *get_ntheaders(HMODULE module) +{ + union { + unsigned long mem; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + } mem; + + //Check if valid dos header + mem.mem = (unsigned long)module; + if (IsBadReadPtr(mem.dos, sizeof(*mem.dos)) || mem.dos->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + //Get and check pe header + mem.mem = rva_to_va(module, mem.dos->e_lfanew); + if (IsBadReadPtr(mem.pe, sizeof(*mem.pe)) || mem.pe->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + return mem.pe; +} + +// +// Returns export table for valid module +// +static IMAGE_EXPORT_DIRECTORY *get_export_table(HMODULE module) +{ + union { + unsigned long mem; + void * pvoid; + IMAGE_DOS_HEADER * dos; + IMAGE_NT_HEADERS * pe; + IMAGE_EXPORT_DIRECTORY * export_dir; + } mem; + + // Check module + mem.pe = get_ntheaders(module); + if (!mem.pe) + return NULL; + + // Check for exports + if (!mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) + return NULL; + + mem.mem = rva_to_va(module, mem.pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if (IsBadReadPtr(mem.export_dir, sizeof(*mem.export_dir))) + return NULL; + + return mem.export_dir; +} + +// +// Sort function for qsort +// +static int sort_names_list(const sort_names_t * A, const sort_names_t * B) +{ + const char *str_A = (const char *)A->name; + const char *str_B = (const char *)B->name; + + return Q_strcmp(str_A, str_B); +} + +// +// Combines meta_module and game_module export tables and replaces meta_module table with new one +// +static bool combine_module_export_tables(CSysModule *meta_module, CSysModule *game_module) +{ + IMAGE_EXPORT_DIRECTORY * exportMM; + IMAGE_EXPORT_DIRECTORY * exportGame; + + unsigned long newNumberOfFunctions; + unsigned long newNumberOfNames; + unsigned long * newFunctions; + unsigned long * newNames; + unsigned short * newNameOrdinals; + sort_names_t * newSort; + + unsigned long i; + unsigned long u; + unsigned long funcCount; + unsigned long nameCount; + unsigned long listFix; + + HMODULE moduleMeta = meta_module->gethandle(); + HMODULE moduleGame = game_module->gethandle(); + + // Get export tables + exportMM = get_export_table(moduleMeta); + exportGame = get_export_table(moduleGame); + if (!exportMM || !exportGame) + { + META_ERROR("Couldn't initialize dynamic linkents, exportMM: %i, exportGame: %i. Exiting...", exportMM, exportGame); + return false; + } + + // setup new export table + newNumberOfFunctions = exportMM->NumberOfFunctions + exportGame->NumberOfFunctions; + newNumberOfNames = exportMM->NumberOfNames + exportGame->NumberOfNames; + + // alloc lists + *(void**)&newFunctions = calloc(1, newNumberOfFunctions * sizeof(*newFunctions)); + *(void**)&newSort = calloc(1, newNumberOfNames * sizeof(*newSort)); + + // copy moduleMeta to new export + for (funcCount = 0; funcCount < exportMM->NumberOfFunctions; funcCount++) + newFunctions[funcCount] = rva_to_va(moduleMeta, ((unsigned long *)rva_to_va(moduleMeta, exportMM->AddressOfFunctions))[funcCount]); + + for (nameCount = 0; nameCount < exportMM->NumberOfNames; nameCount++) + { + // fix name address + newSort[nameCount].name = rva_to_va(moduleMeta, ((unsigned long *)rva_to_va(moduleMeta, exportMM->AddressOfNames))[nameCount]); + + // ordinal is index to function list + newSort[nameCount].nameOrdinal = ((unsigned short *)rva_to_va(moduleMeta, exportMM->AddressOfNameOrdinals))[nameCount]; + } + + // copy moduleGame to new export + for (i = 0; i < exportGame->NumberOfFunctions; i++) + newFunctions[funcCount + i] = rva_to_va(moduleGame, ((unsigned long *)rva_to_va(moduleGame, exportGame->AddressOfFunctions))[i]); + + for (i = 0, listFix = 0; i < exportGame->NumberOfNames; i++) + { + const char *name = (const char *)rva_to_va(moduleGame, ((unsigned long *)rva_to_va(moduleGame, exportGame->AddressOfNames))[i]); + //Check if name already in the list + for (u = 0; u < nameCount; u++) + { + if (!Q_stricmp(name, (const char *)newSort[u].name)) + { + listFix -= 1; + break; + } + } + + if (u < nameCount) //already in the list.. skip + continue; + + newSort[nameCount + i + listFix].name = (unsigned long)name; + newSort[nameCount + i + listFix].nameOrdinal = (unsigned short)funcCount + ((unsigned short *)rva_to_va(moduleGame, exportGame->AddressOfNameOrdinals))[i]; + } + + //set new number + newNumberOfNames = nameCount + i + listFix; + + //sort names list + qsort(newSort, newNumberOfNames, sizeof(*newSort), (int(*)(const void*, const void*))&sort_names_list); + + //make newNames and newNameOrdinals lists (VirtualAlloc so we dont waste heap memory to stuff that isn't freed) + *(void**)&newNames = VirtualAlloc(0, newNumberOfNames * sizeof(*newNames), MEM_COMMIT, PAGE_READWRITE); + *(void**)&newNameOrdinals = VirtualAlloc(0, newNumberOfNames * sizeof(*newNameOrdinals), MEM_COMMIT, PAGE_READWRITE); + + for (i = 0; i < newNumberOfNames; i++) + { + newNames[i] = newSort[i].name; + newNameOrdinals[i] = newSort[i].nameOrdinal; + } + + free(newSort); + + // translate VAs to RVAs + for (i = 0; i < newNumberOfFunctions; i++) + newFunctions[i] = va_to_rva(moduleMeta, newFunctions[i]); + + for (i = 0; i < newNumberOfNames; i++) + { + newNames[i] = va_to_rva(moduleMeta, newNames[i]); + newNameOrdinals[i] = (unsigned short)va_to_rva(moduleMeta, newNameOrdinals[i]); + } + + DWORD OldProtect; + if (!VirtualProtect(exportMM, sizeof(*exportMM), PAGE_READWRITE, &OldProtect)) + { + META_ERROR("Couldn't initialize dynamic linkents, VirtualProtect failed: %i. Exiting...", GetLastError()); + return false; + } + + exportMM->Base = 1; + exportMM->NumberOfFunctions = newNumberOfFunctions; + exportMM->NumberOfNames = newNumberOfNames; + *(unsigned long*)&(exportMM->AddressOfFunctions) = va_to_rva(moduleMeta, newFunctions); + *(unsigned long*)&(exportMM->AddressOfNames) = va_to_rva(moduleMeta, newNames); + *(unsigned long*)&(exportMM->AddressOfNameOrdinals) = va_to_rva(moduleMeta, newNameOrdinals); + + VirtualProtect(exportMM, sizeof(*exportMM), OldProtect, &OldProtect); + return true; +} + +bool meta_init_linkent_replacement(CSysModule *moduleMetamod, CSysModule *moduleGame) +{ + return combine_module_export_tables(moduleMetamod, moduleGame); +}