diff --git a/src/core/extras/gta3/menu.cpp b/src/core/extras/gta3/menu.cpp index 8e04ce4a..fe0e6b1d 100644 --- a/src/core/extras/gta3/menu.cpp +++ b/src/core/extras/gta3/menu.cpp @@ -119,7 +119,7 @@ class TheMenu : public AbstractFrontend // Writes temporary @text into the label @tmplabel and returns it's entry TextEntry TempTextLabel(const std::string& text, const char* tmplabel) { - fxt.set(tmplabel, text.data()); + fxt.set(tmplabel, text.data(), text.size()); return TextLabel(tmplabel); } @@ -215,7 +215,7 @@ void TheMenu::LoadText() return ParseFXT(fxt, lang.data()); }; - this->fxt.add("ML__SM", ""); // Typed search text + this->fxt.add("ML__SM", "", 0); // Typed search text // Try to load a fxt with the OS locale auto locale = GetUserDefaultLCID(); @@ -343,10 +343,11 @@ bool TheMenu::BuildCurrentModsPage(int inc) { auto& modinfo = this->mMods[mCurrentModsPage * NumModsPerPage + i].get(); mCurrentPageMods.emplace_back(modinfo); - fxt.set(entry->m_szName, modinfo.GetName().c_str()); + const std::string& modName = modinfo.GetName(); + fxt.set(entry->m_szName, modName.c_str(), modName.size()); } else - fxt.set(entry->m_szName, ""); + fxt.set(entry->m_szName, "", 0); } return true; @@ -359,7 +360,7 @@ bool TheMenu::BuildCurrentModsPage(int inc) { auto& entry = mPageMods.GetEntry(i)->pEntry; entry->m_nActionType = MENU_ACTION_SKIP; - fxt.set(entry->m_szName, ""); + fxt.set(entry->m_szName, "", 0); } return false; @@ -679,7 +680,8 @@ void TheMenu::ModPageEvents() title.resize(max_title_size - 3); title.append("..."); } - fxt.set("ML_FYHH", modloader::toupper(title).data()); + const std::string& titleUpper = modloader::toupper(title); + fxt.set("ML_FYHH", titleUpper.c_str(), titleUpper.size()); }; // Mod page builder for each entry in the Mods page diff --git a/src/plugins/gta3/std.asi/ModuleInfo.cpp b/src/plugins/gta3/std.asi/ModuleInfo.cpp index 519ffe83..5190b1b6 100644 --- a/src/plugins/gta3/std.asi/ModuleInfo.cpp +++ b/src/plugins/gta3/std.asi/ModuleInfo.cpp @@ -10,6 +10,7 @@ #include "asi.h" #include "args_translator/translator.hpp" #include +#include // for NTSTATUS using namespace modloader; @@ -17,6 +18,118 @@ using namespace modloader; template static bool ModulesWalk(uint32_t pid, F functor); +/* +* DLL Load Notification functions and structs from ntdll.dll +* They are documented as "may be changed or removed without further notice" and we can only import them dynamically +*/ +typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA +{ + ULONG Flags; + PUNICODE_STRING FullDllName; + PUNICODE_STRING BaseDllName; + PVOID DllBase; + ULONG SizeOfImage; +} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; + +typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA +{ + ULONG Flags; + PCUNICODE_STRING FullDllName; + PCUNICODE_STRING BaseDllName; + PVOID DllBase; + ULONG SizeOfImage; +} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + +typedef union _LDR_DLL_NOTIFICATION_DATA +{ + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; +} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; + +typedef const _LDR_DLL_NOTIFICATION_DATA * PCLDR_DLL_NOTIFICATION_DATA; + +typedef VOID (CALLBACK * PLDR_DLL_NOTIFICATION_FUNCTION)( + _In_ ULONG NotificationReason, + _In_ PCLDR_DLL_NOTIFICATION_DATA NotificationData, + _In_opt_ PVOID Context + ); + +typedef _Function_class_(LDR_DLL_NOTIFICATION_FUNCTION) + VOID NTAPI LDR_DLL_NOTIFICATION_FUNCTION( + _In_ ULONG NotificationReason, + _In_ PCLDR_DLL_NOTIFICATION_DATA NotificationData, + _In_opt_ PVOID Context + ); + +static NTSTATUS (NTAPI *pLdrRegisterDllNotification)(_In_ ULONG Flags, _In_ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, + _In_opt_ PVOID Context, _Out_ PVOID *Cookie); + +static NTSTATUS (NTAPI *pLdrUnregisterDllNotification)(_In_ PVOID Cookie); + +/* +* RAII for the DLL Load Notifications +*/ +struct scoped_LdrLoadNotification +{ + scoped_LdrLoadNotification(ThePlugin::ModuleInfo* module, const std::string& path) + : module(module) + { + if (pLdrRegisterDllNotification != nullptr && pLdrRegisterDllNotification != nullptr) + { + MultiByteToWideChar(CP_ACP, 0, path.c_str(), static_cast(path.size()), contextPath, MAX_PATH); + + if (pLdrRegisterDllNotification(0, LdrDllNotification, this, ¬ificationCookie) == 0) + { + plugin_ptr->cast().bLoadingModulesNow = true; + } + else + { + notificationCookie = nullptr; + } + } + } + + ~scoped_LdrLoadNotification() + { + if (notificationCookie != nullptr) + { + plugin_ptr->cast().bLoadingModulesNow = false; + pLdrUnregisterDllNotification(notificationCookie); + } + } + + bool ImportsIntercepted() const { return bImportsIntercepted; } + +private: + static void CALLBACK LdrDllNotification(ULONG NotificationReason, PCLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) + { + static constexpr ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + + if (NotificationReason == LDR_DLL_NOTIFICATION_REASON_LOADED) + { + // We only care about a load of the module we are currently loading, not any transient dependencies + const LDR_DLL_LOADED_NOTIFICATION_DATA& notification = NotificationData->Loaded; + + scoped_LdrLoadNotification* ctx = static_cast(Context); + const size_t dllPathLength = notification.FullDllName->Length / sizeof(notification.FullDllName->Buffer[0]); + if (wcsncmp(ctx->contextPath, notification.FullDllName->Buffer, dllPathLength) == 0) + { + // This will be overwritten later anyway, but we need the module registered early + // for the sake of path redirections from DllMain + ctx->module->module = static_cast(notification.DllBase); + ctx->module->PatchImports(); + ctx->bImportsIntercepted = true; + } + } + } + +private: + wchar_t contextPath[MAX_PATH]; + ThePlugin::ModuleInfo* module; + + PVOID notificationCookie = nullptr; + bool bImportsIntercepted = false; +}; /* @@ -194,6 +307,20 @@ void ThePlugin::LocateCleo() } +/* +* Try to import the DLL Load Notification functions +*/ +void ThePlugin::LocateDllNotificationFuncs() +{ + HMODULE ntdll = LoadLibrary(TEXT("ntdll")); + if (ntdll != nullptr) + { + pLdrRegisterDllNotification = reinterpret_cast(GetProcAddress(ntdll, "LdrRegisterDllNotification")); + pLdrUnregisterDllNotification = reinterpret_cast(GetProcAddress(ntdll, "LdrUnregisterDllNotification")); + } +} + + /* * Loads the module assigned to our field path */ @@ -218,11 +345,25 @@ bool ThePlugin::ModuleInfo::Load() } // Load the library module into our module field + scoped_LdrLoadNotification xldr(this, file->fullpath()); + SetLastError(0); - this->module = bIsMainExecutable? GetModuleHandleA(0) : LoadLibraryA(file->fullpath().c_str()); + this->module = bIsMainExecutable? GetModuleHandle(nullptr) : LoadLibraryA(file->fullpath().c_str()); // Patch the module imports to pass throught args translation. - if(this->module) this->PatchImports(); + // But do it only if the DLL Load Notification didn't do it already. + if(this->module) + { + if(!xldr.ImportsIntercepted()) + { + this->PatchImports(); + plugin_ptr->Log("File \"%s\" imports patched via the Legacy method", file->filepath()); + } + else + { + plugin_ptr->Log("File \"%s\" imports patched via the DLL Load Notification", file->filepath()); + } + } } return this->module != 0; } diff --git a/src/plugins/gta3/std.asi/args_translator/xtranslator_path.hpp b/src/plugins/gta3/std.asi/args_translator/xtranslator_path.hpp index f7a45899..b8625f3a 100644 --- a/src/plugins/gta3/std.asi/args_translator/xtranslator_path.hpp +++ b/src/plugins/gta3/std.asi/args_translator/xtranslator_path.hpp @@ -178,7 +178,11 @@ inline void path_translator_base::CallInfo::TranslatePathForASI(const T*& arg, c bDoTranslation = true; } else - bDoTranslation = true; + { + // If relative, only translate if we're NOT in the process of loading modules, + // as we're already chdir'd to inside the mod directory otherwise. + bDoTranslation = !plugin_ptr->cast().bLoadingModulesNow; + } //---- if(bDoTranslation) diff --git a/src/plugins/gta3/std.asi/asi.cpp b/src/plugins/gta3/std.asi/asi.cpp index 4fb0e481..3bfe0770 100644 --- a/src/plugins/gta3/std.asi/asi.cpp +++ b/src/plugins/gta3/std.asi/asi.cpp @@ -45,8 +45,11 @@ const ThePlugin::info& ThePlugin::GetInfo() */ bool ThePlugin::OnStartup() { + // Try to import the DLL Load Notification functions + this->LocateDllNotificationFuncs(); + // Register GTA module for some arg translation - this->asiList.emplace_front("gta", nullptr, GetModuleHandleA(0)); + this->asiList.emplace_front("gta", nullptr, GetModuleHandle(nullptr)); this->asiList.front().PatchImports(); // Find CLEO.asi diff --git a/src/plugins/gta3/std.asi/asi.h b/src/plugins/gta3/std.asi/asi.h index 31e83e6b..9035c348 100644 --- a/src/plugins/gta3/std.asi/asi.h +++ b/src/plugins/gta3/std.asi/asi.h @@ -124,16 +124,22 @@ class ThePlugin : public modloader::basic_plugin // CLEO.ASI version int iCleoVersion; bool bHasNoCleoFolder; + + // Set to true when loading - do not translate relative paths, as we're chdir'd into modloader already + // Set by the DLL Load Notification + bool bLoadingModulesNow = false; // List of asi files need to load (or loaded) ModuleInfoList asiList; // It's called asiList but it's not limited to .asi files! // List of CLEO scripts (.cs, .cs3, .cs4, .cs5, .cm) CsInfoList csList; // It's called cs but it's not limited to .cs files (e.g. cm files works) - + // Find all cleo plugins already loaded and push them into asi list void LocateCleo(); + // Try to import the DLL Load Notification functions + void LocateDllNotificationFuncs(); }; diff --git a/src/plugins/gta3/std.data/data_traits/handling.cpp b/src/plugins/gta3/std.data/data_traits/handling.cpp index 2818c373..7f880fd4 100644 --- a/src/plugins/gta3/std.data/data_traits/handling.cpp +++ b/src/plugins/gta3/std.data/data_traits/handling.cpp @@ -363,8 +363,11 @@ static auto MakeReadmeReader() -> std::function::call<0x5BD830>, mem_ptr(0xC2B9C8).get()); + auto ReloadHandling = [] + { + void* handlingData = *mem_ptr(0x5BFA95 + 1).get(); + injector::thiscall::call<0x5BD830>(handlingData); + }; // Handling Merger plugin_ptr->AddMerger("handling.cfg", true, false, false, reinstall_since_start, gdir_refresh(ReloadHandling)); diff --git a/src/shared/fxt_parser/fxt.hpp b/src/shared/fxt_parser/fxt.hpp index 738899e8..b219eff6 100644 --- a/src/shared/fxt_parser/fxt.hpp +++ b/src/shared/fxt_parser/fxt.hpp @@ -74,41 +74,42 @@ namespace injector /* * Used on III/VC to get a wchar string */ - static string_container to_wchar(const char* value) + static string_container to_wchar(const char* value, size_t len) { - string_container str; str.reserve((strlen(value) + 1) * 2); - for(auto p = value; *p; ++p) + string_container str; str.reserve((len + 2) * 2); + for(size_t i = 0; i < len; ++i) { - str.emplace_back(*p); str.emplace_back('\0'); // character + str.emplace_back(value[i]); str.emplace_back('\0'); // character } - str.emplace_back('\0'); str.emplace_back('\0'); // null terminator + // GTA III and VC always over-read strings by one character, so give two null terminators + str.insert(str.end(), 4, '\0'); return str; } /* * Used on SA to get a char string */ - static string_container to_char(const char* value) + static string_container to_char(const char* value, size_t len) { - string_container str(value, value + strlen(value) + 1); //+1 for terminator + string_container str(value, value + len + 1); //+1 for terminator return str; } /* * Adds a GXT @key - @value pair to the text map for use in our GxtHook */ - static void add(const char* key, const char* value, hash_type table = 0) + static void add(const char* key, const char* value, size_t len, hash_type table = 0) { if(data().can_patch) patch(); - data().tmap[table][GetHash(key)] = gvm.IsIII() || gvm.IsVC()? to_wchar(value) : to_char(value); + data().tmap[table][GetHash(key)] = gvm.IsIII() || gvm.IsVC()? to_wchar(value, len) : to_char(value, len); } /* * Overrides the specified GXT @key */ - static void set(const char* key, const char* value, hash_type table = 0) + static void set(const char* key, const char* value, size_t len, hash_type table = 0) { - return add(key, value, table); + return add(key, value, len, table); } /* diff --git a/src/shared/fxt_parser/fxt_parser.hpp b/src/shared/fxt_parser/fxt_parser.hpp index eb700c62..69c8256d 100644 --- a/src/shared/fxt_parser/fxt_parser.hpp +++ b/src/shared/fxt_parser/fxt_parser.hpp @@ -65,7 +65,19 @@ namespace injector } // Adds into the text map only if found both key and value - if(key && value) manager.add(key, value, table); + if(key && value) + { + // CLEO trims trailing whitespace from strings, we should do the same + char* lastNonWhitespace = value; + for(char* p = value; *p; ++p) + { + if(!(*p == 0x20 || (*p >= 0x09 && *p <= 0x0D))) // NOT whitespace? + { + lastNonWhitespace = p; + } + } + manager.add(key, value, lastNonWhitespace - value + 1, table); + } } // Done diff --git a/src/translator/gta3/3/10.hpp b/src/translator/gta3/3/10.hpp index 33694b47..a9af5b6b 100644 --- a/src/translator/gta3/3/10.hpp +++ b/src/translator/gta3/3/10.hpp @@ -201,7 +201,8 @@ static void III_10(std::map& map) map[0x5B905E] = 0x4762C2; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadLevel map[0x5BD830] = 0x546DB0; // _ZN16cHandlingDataMgr16LoadHandlingDataEv map[0x5BD850] = 0x546DDE; // call _ZN8CFileMgr12LoadTextFileEPKcPhi ; @cHandlingDataMgr::LoadHandlingData - map[0xC2B9C8] = 0x728060; // mod_HandlingManager CHandlingData + //map[0xC2B9C8] = 0x728060; // mod_HandlingManager CHandlingData + map[0x5BFA95] = 0x48BD77; // mov ecx, offset mod_HandlingManager CHandlingData map[0x5B8428] = 0x476AEB; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadObjectTypes map[0x5B871A] = 0x478393; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadScene map[0x5BF750] = 0x564EA0; // _ZN11CWeaponInfo10InitialiseEv diff --git a/src/translator/gta3/sa/10us.hpp b/src/translator/gta3/sa/10us.hpp index d9ba30a3..0dbc8e23 100644 --- a/src/translator/gta3/sa/10us.hpp +++ b/src/translator/gta3/sa/10us.hpp @@ -223,7 +223,8 @@ static void sa_10us(std::map& map) map[0x5B905E] = 0x5B905E; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadLevel map[0x5BD830] = 0x5BD830; // _ZN16cHandlingDataMgr16LoadHandlingDataEv map[0x5BD850] = 0x5BD850; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @cHandlingDataMgr::LoadHandlingData - map[0xC2B9C8] = 0xC2B9C8; // mod_HandlingManager CHandlingData + //map[0xC2B9C8] = 0xC2B9C8; // mod_HandlingManager CHandlingData + map[0x5BFA95] = 0x5BFA95; // mov ecx, offset mod_HandlingManager CHandlingData map[0x5B8428] = 0x5B8428; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadObjectTypes map[0x5B871A] = 0x5B871A; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadScene map[0x5DD780] = 0x5DD780; // _ZN9CPlantMgr12ReloadConfigEv diff --git a/src/translator/gta3/vc/10.hpp b/src/translator/gta3/vc/10.hpp index bfeda86b..bde9f540 100644 --- a/src/translator/gta3/vc/10.hpp +++ b/src/translator/gta3/vc/10.hpp @@ -248,7 +248,8 @@ static void vc_10(std::map& map) map[0x5B905E] = 0x48D97C; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadLevel map[0x5BD830] = 0x5AAE20; // _ZN16cHandlingDataMgr16LoadHandlingDataEv map[0x5BD850] = 0x5AAE4E; // call _ZN8CFileMgr12LoadTextFileEPKcPhi ; @cHandlingDataMgr::LoadHandlingData - map[0xC2B9C8] = 0x978E58; // mod_HandlingManager CHandlingData + //map[0xC2B9C8] = 0x978E58; // mod_HandlingManager CHandlingData + map[0x5BFA95] = 0x4A5034; // mov ecx, offset mod_HandlingManager CHandlingData map[0x5B8428] = 0x48C846; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadObjectTypes map[0x5B871A] = 0x48B079; // call _ZN8CFileMgr8OpenFileEPKcS1_ ; @CFileLoader::LoadScene map[0x5BF750] = 0x5D5750; // _ZN11CWeaponInfo10InitialiseEv