#define LOG_MODULE "module" #include #include "core/log.h" #include "hook/pe.h" #include "imports/avs.h" #include "imports/eapki.h" #include "launcher/module.h" #include "launcher/property-util.h" #include "util/str.h" #define MM_ALLOCATION_GRANULARITY 0x10000 static bool module_replace_dll_iat(HMODULE hModule, const struct array *iat_hook_dlls) { log_assert(hModule); log_assert(iat_hook_dlls); log_misc("replace dll iat: %p", hModule); if (iat_hook_dlls->nitems == 0) return true; PBYTE pbModule = (PBYTE) hModule; // Find EXE base in process memory IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *) pbModule; IMAGE_NT_HEADERS *inh = (IMAGE_NT_HEADERS *) (pbModule + idh->e_lfanew); // Search through import table if it exists and replace the target DLL with // our DLL filename PIMAGE_SECTION_HEADER pRemoteSectionHeaders = (PIMAGE_SECTION_HEADER) ((PBYTE) pbModule + sizeof(inh->Signature) + sizeof(inh->FileHeader) + inh->FileHeader.SizeOfOptionalHeader); size_t total_size = inh->OptionalHeader.SizeOfHeaders; for (DWORD n = 0; n < inh->FileHeader.NumberOfSections; ++n) { IMAGE_SECTION_HEADER *header = (IMAGE_SECTION_HEADER *) (pRemoteSectionHeaders + n); size_t new_total_size = header->VirtualAddress + header->Misc.VirtualSize; if (new_total_size > total_size) total_size = new_total_size; } void *remote_addr = NULL; size_t remote_addr_ptr = 0; for (size_t i = 0; i < 10000 && remote_addr == NULL; i++) { remote_addr = VirtualAlloc( pbModule + total_size + (MM_ALLOCATION_GRANULARITY * (i + 1)), (MAX_PATH + 1) * iat_hook_dlls->nitems, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); } log_assert(remote_addr != NULL); if (inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress != 0) { PIMAGE_IMPORT_DESCRIPTOR pImageImport = (PIMAGE_IMPORT_DESCRIPTOR) (pbModule + inh->OptionalHeader .DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress); DWORD size = 0; while (inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .Size == 0 || size < inh->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .Size) { IMAGE_IMPORT_DESCRIPTOR *ImageImport = (IMAGE_IMPORT_DESCRIPTOR *) pImageImport; if (ImageImport->Name == 0) { break; } const char *name = (const char *) (pbModule + ImageImport->Name); for (size_t i = 0; i < iat_hook_dlls->nitems; i++) { const char *iat_hook_dll = *array_item(char *, iat_hook_dlls, i); char *iat_hook_replacement = strstr(iat_hook_dll, "="); if (!iat_hook_replacement) continue; *iat_hook_replacement = '\0'; const char *expected_dll = iat_hook_dll; const char *replacement_path_dll = iat_hook_replacement + 1; if (strcmp(name, expected_dll) == 0) { pe_patch( (PBYTE) remote_addr + remote_addr_ptr, replacement_path_dll, strlen(replacement_path_dll)); log_misc( "Replacing %s with %s", name, replacement_path_dll); DWORD val = (DWORD) ((PBYTE) remote_addr - pbModule); pe_patch(&ImageImport->Name, &val, sizeof(DWORD)); pe_patch(pImageImport, &ImageImport, sizeof(ImageImport)); remote_addr_ptr += strlen(replacement_path_dll) + 1; } *iat_hook_replacement = '='; } pImageImport++; size += sizeof(IMAGE_IMPORT_DESCRIPTOR); } } else { log_misc("Couldn't find import table, can't hook DLL\n"); goto inject_fail; } return true; inject_fail: return false; } void module_init(struct module_context *module, const char *path) { log_assert(module != NULL); log_assert(path != NULL); log_info("init: %s", path); module->dll = LoadLibrary(path); if (module->dll == NULL) { LPSTR buffer; DWORD err = GetLastError(); FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &buffer, 0, NULL); if (err == ERROR_MOD_NOT_FOUND) { log_warning("%s is likely missing dependencies", path); log_warning("Do you have vcredist/directx runtimes installed?"); } log_fatal("%s: Failed to load game DLL: %s", path, buffer); LocalFree(buffer); } module->path = str_dup(path); log_misc("init done"); } void module_with_iat_hooks_init( struct module_context *module, const char *path, const struct array *iat_hook_dlls) { log_assert(module != NULL); log_assert(path != NULL); log_info("init iat hooks: %s", path); module->dll = LoadLibraryExA(path, NULL, DONT_RESOLVE_DLL_REFERENCES); if (module->dll == NULL) { LPSTR buffer; DWORD err = GetLastError(); FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &buffer, 0, NULL); if (err == ERROR_MOD_NOT_FOUND) { log_warning("%s is likely missing dependencies", path); log_warning("Do you have vcredist/directx runtimes installed?"); } log_fatal("%s: Failed to load game DLL: %s", path, buffer); LocalFree(buffer); } // Add IAT hooks module_replace_dll_iat(module->dll, iat_hook_dlls); log_misc("Finished processing IAT hooks"); // Resolve all imports like a normally loaded DLL pe_resolve_imports(module->dll); dll_entry_t orig_entry = pe_get_entry_point(module->dll); orig_entry(module->dll, DLL_PROCESS_ATTACH, NULL); log_misc("Finished resolving imports"); module->path = str_dup(path); } void module_init_invoke( const struct module_context *module, struct ea3_ident_config *ea3_ident_config, struct property_node *app_params_node) { char sidcode_short[17]; char sidcode_long[21]; char security_code[9]; dll_entry_init_t init; bool ok; log_info("init invoke"); /* Set up security env vars */ str_format( security_code, lengthof(security_code), "G*%s%s%s%s", ea3_ident_config->model, ea3_ident_config->dest, ea3_ident_config->spec, ea3_ident_config->rev); log_misc("security code: %s", security_code); std_setenv("/env/boot/version", "0.0.0"); std_setenv("/env/profile/security_code", security_code); std_setenv("/env/profile/system_id", ea3_ident_config->pcbid); std_setenv("/env/profile/account_id", ea3_ident_config->pcbid); std_setenv("/env/profile/license_id", ea3_ident_config->softid); std_setenv("/env/profile/software_id", ea3_ident_config->softid); std_setenv("/env/profile/hardware_id", ea3_ident_config->hardid); /* Set up the short sidcode string, let dll_entry_init mangle it */ str_format( sidcode_short, lengthof(sidcode_short), "%s%s%s%s%s", ea3_ident_config->model, ea3_ident_config->dest, ea3_ident_config->spec, ea3_ident_config->rev, ea3_ident_config->ext); log_misc("sidcode short: %s", sidcode_short); /* Set up long-form sidcode env var */ str_format( sidcode_long, lengthof(sidcode_long), "%s:%s:%s:%s:%s", ea3_ident_config->model, ea3_ident_config->dest, ea3_ident_config->spec, ea3_ident_config->rev, ea3_ident_config->ext); log_misc("sidecode long: %s", sidcode_long); /* Set this up beforehand, as certain games require it in dll_entry_init */ std_setenv("/env/profile/soft_id_code", sidcode_long); init = (void *) GetProcAddress(module->dll, "dll_entry_init"); if (init == NULL) { log_fatal( "%s: dll_entry_init not found. Is this a game DLL?", module->path); } log_info("Invoking game init..."); struct property *prop = property_util_cstring_load("p3io"); struct property_node *prop_node = property_search(prop, NULL, "/param"); property_util_log(prop); property_util_node_log(prop_node); ok = init(sidcode_short, prop_node); if (!ok) { log_fatal("%s: dll_module_init() returned failure", module->path); } else { log_info("Game init done"); } /* Back-propagate sidcode, as some games modify it during init */ memcpy( ea3_ident_config->model, sidcode_short + 0, sizeof(ea3_ident_config->model) - 1); ea3_ident_config->dest[0] = sidcode_short[3]; ea3_ident_config->spec[0] = sidcode_short[4]; ea3_ident_config->rev[0] = sidcode_short[5]; memcpy( ea3_ident_config->ext, sidcode_short + 6, sizeof(ea3_ident_config->ext)); /* Set up long-form sidcode env var again */ str_format( sidcode_long, lengthof(sidcode_long), "%s:%s:%s:%s:%s", ea3_ident_config->model, ea3_ident_config->dest, ea3_ident_config->spec, ea3_ident_config->rev, ea3_ident_config->ext); std_setenv("/env/profile/soft_id_code", sidcode_long); log_misc("back-propagated sidcode long: %s", sidcode_long); log_misc("init invoke done"); } bool module_main_invoke(const struct module_context *module) { /* GCC warns if you call a variable "main" */ dll_entry_main_t main_; bool result; log_assert(module != NULL); log_info("main invoke"); main_ = (void *) GetProcAddress(module->dll, "dll_entry_main"); if (main_ == NULL) { log_fatal( "%s: dll_entry_main not found. Is this a game DLL?", module->path); } log_info("Invoking game's main function..."); result = main_(); log_info("Main terminated, result: %d", result); return result; } void module_fini(struct module_context *module) { log_info("fini"); if (module == NULL) { return; } free(module->path); if (module->dll != NULL) { FreeLibrary(module->dll); } log_misc("fini done"); }