diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/usvfs/test/thooklib_test/main.cpp | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/usvfs/test/thooklib_test/main.cpp')
| -rw-r--r-- | libs/usvfs/test/thooklib_test/main.cpp | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/libs/usvfs/test/thooklib_test/main.cpp b/libs/usvfs/test/thooklib_test/main.cpp new file mode 100644 index 0000000..df48198 --- /dev/null +++ b/libs/usvfs/test/thooklib_test/main.cpp @@ -0,0 +1,235 @@ +#include <gtest/gtest.h> +#include <hooklib.h> +#include <iostream> +#include <ttrampolinepool.h> +#include <utility.h> +#include <windows_sane.h> +// #include <boost/thread.hpp> +#include <boost/filesystem.hpp> +#include <exceptionex.h> +#include <spdlog/sinks/stdout_sinks.h> +#include <spdlog/spdlog.h> +#include <winapi.h> + +namespace fs = boost::filesystem; + +#include <stringutils.h> + +using namespace std; +using namespace HookLib; + +class TempFile +{ +public: + TempFile(const wchar_t* relative) : wpath(winapi::wide::getModuleFileName(nullptr)) + { + size_t path_end = wpath.rfind(L'\\'); + if (path_end != std::wstring::npos) { + wpath.erase(path_end + 1); + wpath += L"..\\temp\\"; + wpath += relative; + } else + wpath = relative; + path = + usvfs::shared::string_cast<std::string>(wpath, usvfs::shared::CodePage::UTF8); + } + + const char* c_str() const { return path.c_str(); } + const wchar_t* w_str() const { return wpath.c_str(); } + +private: + std::string path; + std::wstring wpath; +}; + +static const HANDLE MARKERHANDLE = reinterpret_cast<HANDLE>(0x1CC0FFEE); +static const TempFile VALID_FILENAME{L"VALID_FILENAME"}; +static const TempFile INVALID_FILENAME{L"\\<>/"}; + +#include "test_hooks.cpp" + +static bool stubCalled = false; + +void __cdecl CreateFileStub(LPVOID) +{ + stubCalled = true; +} + +class HookingTest : public testing::Test +{ +public: + void SetUp() + { + /* typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink; + boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>(); + + // Add a stream to write log to + sink->locked_backend()->add_stream(boost::make_shared<std::ofstream>("c:\\temp\\testing_out.log")); + + // Register the sink in the logging core + logging::core::get()->add_sink(sink); + + sink->set_filter(expr::attr<LogLevel>("Severity") >= LogLevel::Debug);*/ + } + + void TearDown() {} + +private: +}; + +TEST(GetProcAddressTest, ReturnsValidResults) +{ + HMODULE mh = GetModuleHandleA("KernelBase.dll"); + EXPECT_NE(nullptr, mh); + EXPECT_EQ(GetProcAddress(mh, "CreateFileA"), MyGetProcAddress(mh, "CreateFileA")); +} + +TEST_F(HookingTest, CanHook) +{ + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileW", THCreateFileW_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileW", THCreateFileW_1); + } + EXPECT_NE(INVALID_HOOK, hook); + RemoveHook(hook); +} + +TEST_F(HookingTest, CanStub) +{ + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallStub(k32Mod, "CreateFileW", CreateFileStub); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallStub(k32Mod, "CreateFileW", CreateFileStub); + } + EXPECT_NE(INVALID_HOOK, hook); + RemoveHook(hook); +} + +TEST_F(HookingTest, RemoveHook) +{ + // test that we can remove a hook + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + } + + EXPECT_NE(INVALID_HOOK, hook); + RemoveHook(hook); + HANDLE test = CreateFileA(INVALID_FILENAME.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + EXPECT_EQ(INVALID_HANDLE_VALUE, test); +} + +TEST_F(HookingTest, CreateFileStubTest) +{ + stubCalled = false; + // test if our stub works + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallStub(k32Mod, "CreateFileA", CreateFileStub); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallStub(k32Mod, "CreateFileA", CreateFileStub); + } + EXPECT_NE(INVALID_HOOK, hook); + HANDLE test = CreateFileA(INVALID_FILENAME.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + RemoveHook(hook); + EXPECT_EQ(true, stubCalled); + EXPECT_EQ(INVALID_HANDLE_VALUE, test); +} + +TEST_F(HookingTest, CreateFileHook) +{ + // test if our hook works + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + } + HANDLE test = CreateFileA(INVALID_FILENAME.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + RemoveHook(hook); + EXPECT_EQ(MARKERHANDLE, test); +} + +TEST_F(HookingTest, CreateFileWHook) +{ + // test if our hook works + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileW", THCreateFileW_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileW", THCreateFileW_1); + } + HANDLE test = CreateFileW(INVALID_FILENAME.w_str(), 0x42, 0x43, + (LPSECURITY_ATTRIBUTES)0x44, 0x45, 0x46, (HANDLE)0x47); + RemoveHook(hook); + EXPECT_EQ(MARKERHANDLE, test); +} + +TEST_F(HookingTest, CreateFileHookRecursion) +{ + // test that the trampoline works, so we can call the original function from + // within the hook + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + } + HANDLE test = CreateFileA(VALID_FILENAME.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + RemoveHook(hook); + EXPECT_NE(MARKERHANDLE, test); +} + +TEST_F(HookingTest, Threading) +{ + // test that multiple threads can concurrently call a hooked function without + // incorrect results. + // TODO: this test doesn't reliably find thread-unsafeties + // NOTE: the hooklib currently does not claim that hook installation or removal + // is thread-safe, only the hooked functions shouldn't become less thread-safe by + // being hooked! + + static const int NUM_THREADS = 100; + static const int NUM_TRIES = 1000; + + HMODULE k32Mod = GetModuleHandleA("kernel32.dll"); + HOOKHANDLE hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + if (hook == INVALID_HOOK) { + k32Mod = GetModuleHandleA("kernelbase.dll"); + hook = InstallHook(k32Mod, "CreateFileA", THCreateFileA_1); + } + std::thread threads[NUM_THREADS]; + for (int i = 0; i < NUM_THREADS; ++i) { + threads[i] = std::thread([i] { + for (int count = 0; count < NUM_TRIES; ++count) { + HANDLE test = CreateFileA(INVALID_FILENAME.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + EXPECT_EQ(MARKERHANDLE, test); + } + }); + } + + for (int i = 0; i < NUM_THREADS; ++i) { + threads[i].join(); + } + + RemoveHook(hook); +} + +int main(int argc, char** argv) +{ + auto logger = spdlog::stdout_logger_mt("usvfs"); + logger->set_level(spdlog::level::warn); + TrampolinePool::initialize(); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |
