-
Notifications
You must be signed in to change notification settings - Fork 2
Dependency work and manual module import table resolver #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
63d9cfd
21cb8ab
a4a2d29
7843ad3
dc05143
a1e826c
c146032
1925cfd
4d5a6db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,10 +18,12 @@ | |
| #include <iostream> | ||
| #include <stdexcept> | ||
| #include <tuple> | ||
| #include <map> | ||
|
|
||
| #include "Zenova.h" | ||
| #include "Zenova/Globals.h" | ||
| #include "Zenova/Utils/Utils.h" | ||
| #include "Zenova/Utils/Memory.h" | ||
|
|
||
| #include "MinHook.h" | ||
|
|
||
|
|
@@ -87,6 +89,97 @@ namespace Zenova::PlatformImpl { | |
| FreeLibraryAndExitThread(reinterpret_cast<HMODULE>(CleanupVariables), 0); | ||
| } | ||
| } | ||
|
|
||
| void* LoadModModuleAndResolveImports(const std::string& module) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this function be dropped in favor of just using a single function? I'm not sure what other places we would use "ResolveModModuleImports" |
||
| { | ||
| void* hModule = LoadLibraryExA(module.c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES); | ||
|
ThePixelGamer marked this conversation as resolved.
|
||
| ResolveModModuleImports(hModule, module); | ||
| return hModule; | ||
| } | ||
|
|
||
| typedef BOOL(APIENTRY* DllMainFunction)(HMODULE, DWORD, LPVOID); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switch to using and put it with the if statement |
||
| void ResolveModModuleImports(void* hModule, const std::string& moduleName) | ||
| { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code base uses { on the same line only |
||
| if (!hModule) { | ||
| throw std::invalid_argument(fmt::format("[{}] Could not resolve the imports for the module because it was nullptr.", __FUNCTION__)); | ||
|
raonygamer marked this conversation as resolved.
Outdated
|
||
| Platform::DebugPause(); | ||
| return; | ||
| } | ||
|
|
||
| uintptr_t hModuleAddress = reinterpret_cast<uintptr_t>(hModule); | ||
| PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(hModuleAddress); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could the reinterpret cast be inlined into ntHeader's initialization? |
||
| PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(hModuleAddress + dosHeader->e_lfanew); | ||
|
|
||
| IMAGE_DATA_DIRECTORY importDir = ntHeader->OptionalHeader.DataDirectory[1]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could importDir be inlined into imports initialization and 1 be swapped out with IMAGE_DIRECTORY_ENTRY_IMPORT |
||
| PIMAGE_IMPORT_DESCRIPTOR imports = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(hModuleAddress + importDir.VirtualAddress); | ||
| if (!imports) { | ||
| throw std::exception(fmt::format("[{}] Could not resolve the imports for the module because the imports data directory could not be found in the PE.", __FUNCTION__).c_str()); | ||
| Platform::DebugPause(); | ||
| return; | ||
| } | ||
|
|
||
| std::map<std::pair<uintptr_t, std::string>, ULONGLONG*> symbolTableAddresses; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a std::map here doesn't really make sense, I think we should make a new struct type here and use that with std::vector struct SymbolTableAddress {
uintptr_t moduleBase;
std::string entrySymbol;
ULONGLONG* function;
} |
||
| while (imports->Characteristics != 0) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the while loops be swapped out with a for loop with an empty initialization? for (; cond; post) |
||
| PIMAGE_THUNK_DATA importLookupTable = reinterpret_cast<PIMAGE_THUNK_DATA>(hModuleAddress + imports->OriginalFirstThunk); | ||
| PIMAGE_THUNK_DATA importAddressTable = reinterpret_cast<PIMAGE_THUNK_DATA>(hModuleAddress + imports->FirstThunk); | ||
|
|
||
| if (imports->OriginalFirstThunk && imports->FirstThunk) { | ||
| LPCSTR moduleName = reinterpret_cast<LPCSTR>(hModuleAddress + imports->Name); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this just be swapped out with this? std::string dllName(reinterpret_cast<LPCSTR>(hModuleAddress + imports->Name));
dllName.erase(dllName.length() - 4); |
||
| std::string importModNameId(moduleName); | ||
| { | ||
| size_t dot = importModNameId.find_last_of('.'); | ||
| if (dot != std::string::npos) { | ||
| importModNameId = importModNameId.substr(0, dot); | ||
| } | ||
| } | ||
|
|
||
| uintptr_t moduleBase = Platform::GetModuleBaseAddress(moduleName); | ||
| if (!moduleBase) { | ||
| moduleBase = (uintptr_t)manager.loadMod(importModNameId); | ||
| if (!moduleBase) | ||
| moduleBase = (uintptr_t)LoadLibraryA(moduleName); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be swapped for Platform::LoadModule |
||
| } | ||
|
|
||
| while (importLookupTable->u1.AddressOfData != 0) { | ||
| if (!(importLookupTable->u1.Ordinal & IMAGE_ORDINAL_FLAG)) { | ||
| PIMAGE_IMPORT_BY_NAME nameData = | ||
| reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(hModuleAddress + importLookupTable->u1.AddressOfData); | ||
|
|
||
| symbolTableAddresses.insert({ { moduleBase, nameData->Name }, &(importAddressTable->u1.Function) }); | ||
| } | ||
| importLookupTable++; | ||
| importAddressTable++; | ||
| } | ||
| } | ||
|
|
||
| imports++; | ||
| } | ||
|
|
||
| for (auto& [infos, function] : symbolTableAddresses) { | ||
| auto& [moduleBase, entrySymbol] = infos; | ||
| if (moduleBase) { | ||
| ULONGLONG procAddress = reinterpret_cast<ULONGLONG>( | ||
| Platform::GetModuleFunction(reinterpret_cast<void*>(moduleBase), entrySymbol.c_str())); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the c_str should be dropped as it's forcing the creation of another string |
||
|
|
||
| if (procAddress && function) { | ||
| Memory::WriteOnProtectedAddress(function, &procAddress, sizeof(ULONGLONG)); | ||
| } | ||
| } | ||
| else { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this error handling should be moved up to where moduleBase is set, nothing else is effectively executed anyways if it's null |
||
| throw std::exception(fmt::format("[{}] Could not resolve the address for '{}' because the module could not be loaded.", __FUNCTION__, entrySymbol).c_str()); | ||
| Platform::DebugPause(); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| if (ntHeader->OptionalHeader.AddressOfEntryPoint) { | ||
| reinterpret_cast<DllMainFunction>(hModuleAddress + ntHeader->OptionalHeader.AddressOfEntryPoint)( | ||
| reinterpret_cast<HMODULE>(hModule), | ||
| DLL_PROCESS_ATTACH, | ||
| nullptr | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| namespace Zenova { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,32 +102,40 @@ namespace Zenova { | |
| load(profile); | ||
| } | ||
|
|
||
| void Manager::loadMod(const std::string& modName) | ||
| void* Manager::loadMod(const std::string& modName) | ||
| { | ||
| if (std::find_if(mods.begin(), mods.end(), | ||
| [&modName](const ModInfo& mod) { return mod.mNameId == modName; }) != mods.end()) | ||
| return; | ||
| return nullptr; | ||
|
|
||
| logger.info("Loading {}", modName); | ||
|
|
||
| // todo: verify path | ||
| std::string folder = manager.dataFolder + "\\mods\\" + modName + "\\"; | ||
| ModInfo mod(folder); | ||
| void* modHandle = mod.loadModule(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modHandle should be dropped in favor of using mod.mHandle |
||
|
|
||
| if (mod.loadModule()) { | ||
| if (modHandle) { | ||
| ModContext ctx = { folder }; | ||
| CALL_MOD_FUNC(mod, ModLoad, ctx) | ||
|
|
||
| PackManager::addMod(folder); | ||
|
|
||
| mods.push_back(std::move(mod)); | ||
| return modHandle; | ||
| } | ||
| else { | ||
| logger.warn("Failed to load {}", mod.mName); | ||
| Platform::ErrorPrinter(); | ||
| return nullptr; | ||
| } | ||
| } | ||
|
|
||
| std::vector<ModInfo>& Manager::getMods() | ||
|
raonygamer marked this conversation as resolved.
Outdated
|
||
| { | ||
| return mods; | ||
| } | ||
|
|
||
| std::string Manager::getVersion() { | ||
| return launched.versionId; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| #include "ModInfo.h" | ||
| #include "Zenova/Profile/ModInfo.h" | ||
|
|
||
| #include <fstream> | ||
| #include <utility> | ||
|
|
@@ -17,14 +17,6 @@ namespace Zenova { | |
| mName = JsonHelper::FindString(modDocument, "name"); | ||
| mDescription = JsonHelper::FindString(modDocument, "description"); | ||
| mVersion = JsonHelper::FindString(modDocument, "version"); | ||
| auto& dependenciesObject = JsonHelper::FindMember(modDocument, "dependencies", false); | ||
| if (!dependenciesObject.IsNull() && dependenciesObject.IsArray()) | ||
| { | ||
| for (auto& object : dependenciesObject.GetArray()) | ||
| { | ||
| mDependencies.push_back(object.GetString()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -34,8 +26,7 @@ namespace Zenova { | |
| mNameId(std::move(mod.mNameId)), | ||
| mName(std::move(mod.mName)), | ||
| mDescription(std::move(mod.mDescription)), | ||
| mVersion(std::move(mod.mVersion)), | ||
| mDependencies(std::move(mod.mDependencies)) | ||
| mVersion(std::move(mod.mVersion)) | ||
| {} | ||
|
|
||
| ModInfo::~ModInfo() { | ||
|
|
@@ -44,16 +35,7 @@ namespace Zenova { | |
|
|
||
| void* ModInfo::loadModule() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could loadModule be inlined into Manager::loadMod |
||
| { | ||
| loadDependencies(); | ||
| mHandle = Platform::LoadModule(mModFolder + mNameId); | ||
| mHandle = Zenova::PlatformImpl::LoadModModuleAndResolveImports(mModFolder + mNameId); | ||
|
raonygamer marked this conversation as resolved.
Outdated
|
||
| return mHandle; | ||
| } | ||
|
|
||
| void ModInfo::loadDependencies() const | ||
| { | ||
| for (auto& dependencyName : mDependencies) | ||
| { | ||
| manager.loadMod(dependencyName); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| #include "Memory.h" | ||
| #include "Windows.h" | ||
|
|
||
| namespace Zenova { | ||
| void Memory::WriteOnProtectedAddress(void* address, void* data, size_t size) { | ||
|
raonygamer marked this conversation as resolved.
Outdated
|
||
| if (!address || !data || !size) | ||
| return; | ||
|
|
||
| DWORD old = 0; | ||
| VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &old); | ||
| memcpy_s(address, size, data, size); | ||
| VirtualProtect(address, size, old, NULL); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| #pragma once | ||
|
|
||
| namespace Zenova { | ||
| class Memory { | ||
| public: | ||
| static void WriteOnProtectedAddress(void* address, void* data, size_t dataSize); | ||
| }; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.