diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-15 17:12:41 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-05-16 12:56:48 -0500 |
| commit | d55f61bd392d7e1e00dfbcb236aabfbe6f98589e (patch) | |
| tree | fc1440da603a1ce49cdff6655977cee674f6da32 | |
| parent | fa0579c7f5703aba9669f7f41900c67e9759fca0 (diff) | |
vfs: drop experimental PE-side bridge / shim DLL stack
Removes the FUSE-bypass fast-path: fluorine_vfs.dll + fluorine_vfs_hid.dll
(mingw-cross PE-side AppInit_DLLs injector with MinHook), the LD_PRELOAD
helper libfluorine_vfs_preload.so, the bridge-index export from FuseConnector,
the prewarm + Wine-loader-trace plumbing in ProtonLauncher, AppInit_DLLs
prefix-init staging, the per-executable UseVfsBridge flag plus its
checkbox/handlers in editexecutablesdialog, and the third_party/minhook
vendored tree.
Core FUSE VFS (mo2filesystem, vfstree, inodetable, overwritemanager,
scancache, trackedwrites) is untouched - this only removes the sidecar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 files changed, 12 insertions, 8547 deletions
diff --git a/src/src/CMakeLists.txt b/src/src/CMakeLists.txt index ed43c8f..8c67f6e 100644 --- a/src/src/CMakeLists.txt +++ b/src/src/CMakeLists.txt @@ -74,87 +74,6 @@ add_executable(organizer ${ORGANIZER_UI} ${ORGANIZER_QRC}) -add_library(fluorine_vfs_preload SHARED - ${CMAKE_CURRENT_SOURCE_DIR}/vfs/vfsbridgepreload.c) - -set_target_properties(fluorine_vfs_preload PROPERTIES - OUTPUT_NAME "fluorine_vfs_preload") - -target_link_libraries(fluorine_vfs_preload PRIVATE dl) - -# ── PE-side VFS injector (fluorine_vfs.dll) ────────────────────────────── -# Cross-compiled with mingw-w64 and loaded into Wine PE processes via the -# AppInit_DLLs registry mechanism set up at prefix init. A game-directory -# hid.dll proxy is also built for Wine paths that do not honor AppInit_DLLs. -find_program(MINGW_CC NAMES x86_64-w64-mingw32-gcc-posix x86_64-w64-mingw32-gcc) -if(MINGW_CC) - set(FL_VFS_DLL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/vfs/fluorine_vfs_inject.c) - set(FL_VFS_HOOKS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/vfs/fluorine_vfs_hooks.c) - set(FL_VFS_MINHOOK_SRC - ${CMAKE_SOURCE_DIR}/third_party/minhook/src/buffer.c - ${CMAKE_SOURCE_DIR}/third_party/minhook/src/hook.c - ${CMAKE_SOURCE_DIR}/third_party/minhook/src/trampoline.c - ${CMAKE_SOURCE_DIR}/third_party/minhook/src/hde/hde64.c) - set(FL_VFS_MINGW_INCLUDES - -I${CMAKE_SOURCE_DIR}/third_party/minhook/include - -I${CMAKE_SOURCE_DIR}/third_party/minhook/src - -I${CMAKE_SOURCE_DIR}/third_party/minhook/src/hde) - set(FL_VFS_DLL ${CMAKE_CURRENT_BINARY_DIR}/fluorine_vfs.dll) - set(FL_VFS_HID_DLL ${CMAKE_CURRENT_BINARY_DIR}/fluorine_vfs_hid.dll) - add_custom_command( - OUTPUT ${FL_VFS_DLL} - COMMAND ${MINGW_CC} - -O2 -Wall -Wextra - ${FL_VFS_MINGW_INCLUDES} - -shared -fno-asynchronous-unwind-tables - -static -static-libgcc - -Wl,--subsystem,windows -Wl,--enable-stdcall-fixup - -Wl,--exclude-all-symbols - -o ${FL_VFS_DLL} - ${FL_VFS_DLL_SRC} - ${FL_VFS_HOOKS_SRC} - ${FL_VFS_MINHOOK_SRC} - # kernel32 only. AppInit_DLLs DLLs run inside user32's - # DllMain under the loader lock — calling into user32 (or any - # other DLL whose DllMain hasn't run yet) wedges every PE - # process in the prefix. - -lkernel32 - DEPENDS ${FL_VFS_DLL_SRC} - ${FL_VFS_HOOKS_SRC} - ${FL_VFS_MINHOOK_SRC} - COMMENT "[mingw] fluorine_vfs.dll" - VERBATIM) - add_custom_command( - OUTPUT ${FL_VFS_HID_DLL} - COMMAND ${MINGW_CC} - -O2 -Wall -Wextra -Wno-cast-function-type - ${FL_VFS_MINGW_INCLUDES} - -shared -fno-asynchronous-unwind-tables - -static -static-libgcc - -DFLUORINE_VFS_PROXY_HID - -Wl,--subsystem,windows -Wl,--enable-stdcall-fixup - -Wl,--exclude-all-symbols - -o ${FL_VFS_HID_DLL} - ${FL_VFS_DLL_SRC} - ${FL_VFS_HOOKS_SRC} - ${FL_VFS_MINHOOK_SRC} - # Game-dir proxy fallback for Wine builds that ignore - # AppInit_DLLs. DllMain still stays kernel32-only; the - # hid exports forward to the real system DLL after load. - -lkernel32 - DEPENDS ${FL_VFS_DLL_SRC} - ${FL_VFS_HOOKS_SRC} - ${FL_VFS_MINHOOK_SRC} - COMMENT "[mingw] fluorine_vfs_hid.dll" - VERBATIM) - add_custom_target(fluorine_vfs_dll ALL DEPENDS ${FL_VFS_DLL} ${FL_VFS_HID_DLL}) - message(STATUS "[Fluorine] mingw-w64 found at ${MINGW_CC} - building fluorine_vfs.dll") -else() - message(WARNING - "[Fluorine] mingw-w64 (x86_64-w64-mingw32-gcc) not found - " - "skipping fluorine_vfs.dll. PE-side VFS injection will be unavailable.") -endif() - set_target_properties(organizer PROPERTIES OUTPUT_NAME "ModOrganizer" AUTOMOC ON diff --git a/src/src/editexecutablesdialog.cpp b/src/src/editexecutablesdialog.cpp index 4b8a478..723b3f5 100644 --- a/src/src/editexecutablesdialog.cpp +++ b/src/src/editexecutablesdialog.cpp @@ -129,9 +129,6 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, connect(ui->useTerminal, &QCheckBox::toggled, [&] {
save();
});
- connect(ui->useVfsBridge, &QCheckBox::toggled, [&] {
- save();
- });
connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&] {
saveOrder();
});
@@ -401,8 +398,6 @@ void EditExecutablesDialog::clearEdits() ui->useProton->setChecked(true);
ui->useTerminal->setEnabled(false);
ui->useTerminal->setChecked(false);
- ui->useVfsBridge->setEnabled(false);
- ui->useVfsBridge->setChecked(false);
m_lastGoodTitle = "";
}
@@ -421,7 +416,6 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->hide->setChecked(e.hide());
ui->useProton->setChecked(e.useProton());
ui->useTerminal->setChecked(e.useTerminal());
- ui->useVfsBridge->setChecked(e.useVfsBridge());
m_lastGoodTitle = e.title();
@@ -470,7 +464,6 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->hide->setEnabled(true);
ui->useProton->setEnabled(true);
ui->useTerminal->setEnabled(true);
- ui->useVfsBridge->setEnabled(true);
}
void EditExecutablesDialog::save()
@@ -554,12 +547,6 @@ void EditExecutablesDialog::save() e->flags(e->flags() & (~Executable::UseTerminal));
}
- if (ui->useVfsBridge->isChecked()) {
- e->flags(e->flags() | Executable::UseVfsBridge);
- } else {
- e->flags(e->flags() & (~Executable::UseVfsBridge));
- }
-
setDirty(true);
}
diff --git a/src/src/editexecutablesdialog.ui b/src/src/editexecutablesdialog.ui index f323558..8ac01f6 100644 --- a/src/src/editexecutablesdialog.ui +++ b/src/src/editexecutablesdialog.ui @@ -488,19 +488,6 @@ Right now the only case I know of where this needs to be overwritten is for the </widget>
</item>
<item>
- <widget class="QCheckBox" name="useVfsBridge">
- <property name="toolTip">
- <string>Inject the PE-side VFS bridge (fluorine_vfs.dll) so file lookups in the game's data dir hit a static index instead of going through Wine + FUSE every time. Saves several seconds on first boot for large modlists. Off by default — some modlists crash with the bridge enabled until indexer/negcache work is finished.</string>
- </property>
- <property name="text">
- <string>Use VFS bridge (experimental)</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>(*) Profile specific</string>
diff --git a/src/src/executableslist.cpp b/src/src/executableslist.cpp index 49b9e86..ca4f295 100644 --- a/src/src/executableslist.cpp +++ b/src/src/executableslist.cpp @@ -96,12 +96,6 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) if (map["useTerminal"].toBool()) flags |= Executable::UseTerminal; - // Default to UseVfsBridge=false. The PE-side bridge is opt-in - // because some modlists (notably ones with NPCWaterAIFix) crash - // with it enabled until the indexer/negcache work is finished. - if (map.contains("useVfsBridge") && map["useVfsBridge"].toBool()) - flags |= Executable::UseVfsBridge; - if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; @@ -142,7 +136,6 @@ void ExecutablesList::store(Settings& s) map["minimizeToSystemTray"] = item.minimizeToSystemTray(); map["useProton"] = item.useProton(); map["useTerminal"] = item.useTerminal(); - map["useVfsBridge"] = item.useVfsBridge(); v.push_back(std::move(map)); } @@ -518,11 +511,6 @@ bool Executable::useTerminal() const return m_flags.testFlag(UseTerminal); } -bool Executable::useVfsBridge() const -{ - return m_flags.testFlag(UseVfsBridge); -} - void Executable::mergeFrom(const Executable& other) { // this happens after executables are loaded from settings and plugin diff --git a/src/src/executableslist.h b/src/src/executableslist.h index 86a5476..8ec8cbb 100644 --- a/src/src/executableslist.h +++ b/src/src/executableslist.h @@ -50,8 +50,7 @@ public: Hide = 0x08,
MinimizeToSystemTray = 0x16,
UseProton = 0x20,
- UseTerminal = 0x40,
- UseVfsBridge = 0x80
+ UseTerminal = 0x40
};
Q_DECLARE_FLAGS(Flags, Flag);
@@ -84,7 +83,6 @@ public: bool hide() const override;
bool useProton() const;
bool useTerminal() const;
- bool useVfsBridge() const;
void mergeFrom(const Executable& other);
diff --git a/src/src/fluorinepaths.cpp b/src/src/fluorinepaths.cpp index e00f11a..b8c2dc8 100644 --- a/src/src/fluorinepaths.cpp +++ b/src/src/fluorinepaths.cpp @@ -24,52 +24,6 @@ QString fluorineVfsCacheDir() return fluorineDataDir() + "/vfs_cache"; } -QString fluorineVfsBridgeDir() -{ - return fluorineDataDir() + "/vfs_bridge"; -} - -QString fluorineVfsInjectDllPath() -{ - static const QString filename = QStringLiteral("fluorine_vfs.dll"); - - // Allow MO2_LIBS_DIR override for ad-hoc test builds; mirrors the - // logic in fluorineVfsPreloadPath(). Falls back to the bundled - // location next to the binary. - if (const char* env = std::getenv("MO2_LIBS_DIR")) { - if (env[0] != '\0') { - const QString candidate = QDir(QString::fromLocal8Bit(env)).filePath(filename); - if (QFileInfo::exists(candidate)) { - return candidate; - } - } - } - - const QString candidate = - QDir(QCoreApplication::applicationDirPath()).filePath( - QStringLiteral("wine/") + filename); - return QFileInfo::exists(candidate) ? candidate : QString(); -} - -QString fluorineVfsHidProxyDllPath() -{ - static const QString filename = QStringLiteral("fluorine_vfs_hid.dll"); - - if (const char* env = std::getenv("MO2_LIBS_DIR")) { - if (env[0] != '\0') { - const QString candidate = QDir(QString::fromLocal8Bit(env)).filePath(filename); - if (QFileInfo::exists(candidate)) { - return candidate; - } - } - } - - const QString candidate = - QDir(QCoreApplication::applicationDirPath()).filePath( - QStringLiteral("wine/") + filename); - return QFileInfo::exists(candidate) ? candidate : QString(); -} - void fluorineMigrateDataDir() { const QString oldRoot = OldFlatpakRoot; diff --git a/src/src/fluorinepaths.h b/src/src/fluorinepaths.h index b4928f5..eef60b2 100644 --- a/src/src/fluorinepaths.h +++ b/src/src/fluorinepaths.h @@ -10,21 +10,6 @@ QString fluorineDataDir(); /// Created on demand by the cache writer. QString fluorineVfsCacheDir(); -/// Returns the VFS bridge index directory: ~/.local/share/fluorine/vfs_bridge -/// Created on demand by the bridge index writer. -QString fluorineVfsBridgeDir(); - -/// Returns the path to the bundled PE-side VFS injector -/// (`fluorine_vfs.dll`), or an empty string if it was not built (e.g. -/// mingw missing in the build image). The DLL is staged to the -/// prefix's `c:\\windows\\system32\\` and registered in AppInit_DLLs at -/// prefix init. -QString fluorineVfsInjectDllPath(); - -/// Returns the bundled low-collision `hid.dll` game-directory proxy for -/// Wine builds that ignore AppInit_DLLs, or an empty string if it was not built. -QString fluorineVfsHidProxyDllPath(); - /// One-time migration from ~/.var/app/com.fluorine.manager/ to /// ~/.local/share/fluorine/. Call before initLogging(). void fluorineMigrateDataDir(); diff --git a/src/src/fuseconnector.cpp b/src/src/fuseconnector.cpp index 59ec92a..f66d1bb 100644 --- a/src/src/fuseconnector.cpp +++ b/src/src/fuseconnector.cpp @@ -2,7 +2,6 @@ #include "settings.h" #include "vfs/scancache.h" -#include "vfs/vfsbridge.h" #include "vfs/vfstree.h" #include <QCoreApplication> @@ -56,19 +55,6 @@ namespace { namespace fs = std::filesystem; -bool envFlagEnabled(const char* name) -{ - const QString value = qEnvironmentVariable(name).trimmed(); - return value == "1" || value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 || - value.compare(QStringLiteral("yes"), Qt::CaseInsensitive) == 0; -} - -bool vfsBridgeExportRequested() -{ - return !envFlagEnabled("FLUORINE_DISABLE_VFS_BRIDGE") && - !envFlagEnabled("FLUORINE_DISABLE_VFS_PRELOAD"); -} - std::string decodeProcMountField(const std::string& in) { std::string out; @@ -460,8 +446,6 @@ bool FuseConnector::mount( stampPluginTimestamps(*tree, m_pluginLoadOrder); } - exportVfsBridgeIndex(*tree); - { const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - treeStart).count(); @@ -668,48 +652,6 @@ std::shared_ptr<TrackedWrites> FuseConnector::trackedWrites() const return m_trackedWrites; } -QString FuseConnector::vfsBridgeIndexPath() const -{ - return QString::fromStdString(m_vfsBridgeIndexPath); -} - -QString FuseConnector::vfsBridgeDataDir() const -{ - return QString::fromStdString(m_dataDirPath); -} - -QString FuseConnector::vfsBridgeMountPoint() const -{ - return QString::fromStdString(m_mountPoint); -} - -void FuseConnector::exportVfsBridgeIndex(const VfsTree& tree) -{ - if (!vfsBridgeExportRequested()) { - m_vfsBridgeIndexPath.clear(); - return; - } - - const auto exportStart = std::chrono::steady_clock::now(); - const auto path = ::vfsBridgeIndexPath(m_dataDirPath, m_overwriteDir, m_lastMods); - auto result = ::exportVfsBridgeIndex(tree, path, m_dataDirPath, m_overwriteDir, - m_mountPoint); - - const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::steady_clock::now() - exportStart).count(); - if (result.ok) { - m_vfsBridgeIndexPath = result.path.string(); - std::fprintf(stderr, - "[VFS] [bridge] exported %zu records to '%s' in %lldms\n", - result.records_written, m_vfsBridgeIndexPath.c_str(), - static_cast<long long>(ms)); - } else { - m_vfsBridgeIndexPath.clear(); - std::fprintf(stderr, "[VFS] [bridge] export failed for '%s': %s\n", - result.path.string().c_str(), result.error.c_str()); - } -} - void FuseConnector::rebuild( const std::vector<std::pair<std::string, std::string>>& mods, const QString& overwrite_dir, const QString& data_dir_name) @@ -749,8 +691,6 @@ void FuseConnector::rebuild( stampPluginTimestamps(*newTree, m_pluginLoadOrder); } - exportVfsBridgeIndex(*newTree); - { std::unique_lock const lock(m_context->tree_mutex); m_context->tree.swap(newTree); diff --git a/src/src/fuseconnector.h b/src/src/fuseconnector.h index e67a498..1f6d1fa 100644 --- a/src/src/fuseconnector.h +++ b/src/src/fuseconnector.h @@ -43,9 +43,6 @@ public: void setPluginLoadOrder(const std::vector<std::string>& load_order); void setTrackingFilePath(const std::string& path); std::shared_ptr<TrackedWrites> trackedWrites() const; - QString vfsBridgeIndexPath() const; - QString vfsBridgeDataDir() const; - QString vfsBridgeMountPoint() const; void unmount(); void discardStagingOnUnmount(); @@ -73,7 +70,6 @@ public: void clearRootFiles(); private: - void exportVfsBridgeIndex(const VfsTree& tree); void flushStaging(); void deployExternalMappings(const MappingType& mapping, const QString& dataDir); void cleanupExternalMappings(); @@ -85,7 +81,6 @@ private: std::string m_gameDir; std::string m_dataDirName; std::string m_dataDirPath; - std::string m_vfsBridgeIndexPath; int m_backingFd = -1; std::vector<CachedBaseFile> m_baseFileCache; std::string m_cachedDataDirPath; diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index a6cbd12..0ce5d90 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -2473,16 +2473,11 @@ bool OrganizerCore::beforeRun( const QFileInfo& binary, const QDir& cwd, const QString& arguments, const QString& profileName, const QString& customOverwrite, const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries, - QString* saveBindMountSource, QString* saveBindMountTarget, - QString* vfsBridgeIndexPath, QString* vfsBridgeDataDir, - QString* vfsBridgeMountPoint) + QString* saveBindMountSource, QString* saveBindMountTarget) { saveCurrentProfile(); if (saveBindMountSource) saveBindMountSource->clear(); if (saveBindMountTarget) saveBindMountTarget->clear(); - if (vfsBridgeIndexPath) vfsBridgeIndexPath->clear(); - if (vfsBridgeDataDir) vfsBridgeDataDir->clear(); - if (vfsBridgeMountPoint) vfsBridgeMountPoint->clear(); // need to wait until directory structure is ready if (m_DirectoryUpdate) { @@ -2522,15 +2517,6 @@ bool OrganizerCore::beforeRun( try { m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); m_USVFS.updateForcedLibraries(forcedLibraries); - if (vfsBridgeIndexPath) { - *vfsBridgeIndexPath = m_USVFS.vfsBridgeIndexPath(); - } - if (vfsBridgeDataDir) { - *vfsBridgeDataDir = m_USVFS.vfsBridgeDataDir(); - } - if (vfsBridgeMountPoint) { - *vfsBridgeMountPoint = m_USVFS.vfsBridgeMountPoint(); - } } catch (const FuseConnectorException& e) { log::error("VFS mount failed: {}", e.what()); return false; diff --git a/src/src/organizercore.h b/src/src/organizercore.h index d786113..7ef7713 100644 --- a/src/src/organizercore.h +++ b/src/src/organizercore.h @@ -311,14 +311,11 @@ public: ProcessRunner processRunner();
- bool beforeRun(const QFileInfo& binary, const QDir& cwd, const QString& arguments, - const QString& profileName, const QString& customOverwrite, - const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries, - QString* saveBindMountSource = nullptr, - QString* saveBindMountTarget = nullptr, - QString* vfsBridgeIndexPath = nullptr, - QString* vfsBridgeDataDir = nullptr, - QString* vfsBridgeMountPoint = nullptr); + bool beforeRun(const QFileInfo& binary, const QDir& cwd, const QString& arguments,
+ const QString& profileName, const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries,
+ QString* saveBindMountSource = nullptr,
+ QString* saveBindMountTarget = nullptr);
bool checkGameRegistryKey();
diff --git a/src/src/prefixsetuprunner.cpp b/src/src/prefixsetuprunner.cpp index fef590b..e57a9c9 100644 --- a/src/src/prefixsetuprunner.cpp +++ b/src/src/prefixsetuprunner.cpp @@ -455,13 +455,6 @@ void PrefixSetupRunner::buildStepList() addStep("post_setup", "Post-Setup (symlinks, dxvk)", [this] { return stepPostSetup(); }); - - // PE-side VFS injector: stage fluorine_vfs.dll into system32 and - // register it in AppInit_DLLs so every Wine PE process loads it. - // Skipped silently when the DLL is not bundled (mingw missing in - // build image). - addStep("vfs_inject", "PE-side VFS Injector", - [this] { return stepVfsInject(); }); } // ============================================================================ @@ -1532,89 +1525,6 @@ bool PrefixSetupRunner::stepPostSetup() return true; } -bool PrefixSetupRunner::stepVfsInject() -{ - // Stage fluorine_vfs.dll into c:\windows\system32 and register it in - // AppInit_DLLs so every PE process in this prefix loads it. Replaces - // the old libfluorine_vfs_preload.so LD_PRELOAD path, which couldn't - // intercept Wine's NTDLL syscall path. - const QString srcDll = fluorineVfsInjectDllPath(); - if (srcDll.isEmpty()) { - emit logMessage("fluorine_vfs.dll not bundled (mingw missing in build " - "image); skipping AppInit_DLLs registration"); - m_steps.last().status = SetupStep::Skipped; - return true; - } - - const QString system32 = m_prefixPath + "/drive_c/windows/system32"; - if (!QDir(system32).exists()) { - m_steps.last().errorMessage = - QStringLiteral("system32 not found at '%1'").arg(system32); - return false; - } - - const QString dstDll = system32 + "/fluorine_vfs.dll"; - QFile::remove(dstDll); - if (!QFile::copy(srcDll, dstDll)) { - m_steps.last().errorMessage = - QStringLiteral("failed to copy fluorine_vfs.dll into '%1'").arg(system32); - return false; - } - emit logMessage(QStringLiteral("Staged fluorine_vfs.dll -> %1").arg(dstDll)); - - // AppInit_DLLs is read by user32 on init. REG_SZ of the DLL basename; - // LoadAppInit_DLLs=1 enables the mechanism; RequireSignedAppInit_DLLs=0 - // disables Vista+ signing requirement (Wine ignores it, but set it - // anyway for defence-in-depth). - const QString tmpDir = fluorineTmpDir(); - QDir().mkpath(tmpDir); - const QString regFile = tmpDir + "/fluorine_vfs_appinit.reg"; - { - QFile f(regFile); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - m_steps.last().errorMessage = "failed to write registry import file"; - return false; - } - // Register only in the 64-bit hive. fluorine_vfs.dll is built as - // 64-bit PE; if Wow6432Node also pointed at it, 32-bit Wine helpers - // (services.exe, winedevice.exe, etc.) would attempt to LoadLibrary - // the missing syswow64\fluorine_vfs.dll and the loader would abort - // EXE init, killing every 32-bit process in the prefix. Also - // explicitly delete any pre-existing Wow6432Node values left over - // from older Fluorine builds that registered both views. - f.write( - "Windows Registry Editor Version 5.00\n\n" - "[HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT" - "\\CurrentVersion\\Windows]\n" - "\"AppInit_DLLs\"=\"fluorine_vfs.dll\"\n" - "\"LoadAppInit_DLLs\"=dword:00000001\n" - "\"RequireSignedAppInit_DLLs\"=dword:00000000\n\n" - "[HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\Windows NT" - "\\CurrentVersion\\Windows]\n" - "\"AppInit_DLLs\"=-\n" - "\"LoadAppInit_DLLs\"=-\n" - "\"RequireSignedAppInit_DLLs\"=-\n"); - f.close(); - } - - QMap<QString, QString> env = baseWineEnv(); - env["WINEDLLOVERRIDES"] = "mshtml=d"; - env["PROTON_USE_XALIA"] = "0"; - // Don't load our own DLL into regedit during the registration call. - env["FLUORINE_DISABLE_VFS_INJECT"] = "1"; - - const int rc = runProcess(m_wineBin, {"regedit", regFile}, env); - QFile::remove(regFile); - if (rc != 0) { - m_steps.last().errorMessage = - QStringLiteral("wine regedit failed for AppInit_DLLs (exit %1)").arg(rc); - return false; - } - - emit logMessage("AppInit_DLLs registered: fluorine_vfs.dll"); - return true; -} - // ============================================================================ // Tool management // ============================================================================ diff --git a/src/src/prefixsetuprunner.h b/src/src/prefixsetuprunner.h index be80462..3797c40 100644 --- a/src/src/prefixsetuprunner.h +++ b/src/src/prefixsetuprunner.h @@ -104,7 +104,6 @@ private: bool stepWineRegistry(); bool stepWin11Mode(); bool stepPostSetup(); - bool stepVfsInject(); // -- DirectX cab extraction helpers ---------------------------------------- bool ensureDirectXRedist(QString& redistPath); diff --git a/src/src/processrunner.cpp b/src/src/processrunner.cpp index 684ca92..91176fd 100644 --- a/src/src/processrunner.cpp +++ b/src/src/processrunner.cpp @@ -977,7 +977,6 @@ ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) m_sp.useProton = exe.useProton(); m_sp.useTerminal = exe.useTerminal(); - m_sp.useVfsBridge = exe.useVfsBridge(); return *this; } @@ -1166,9 +1165,7 @@ std::optional<ProcessRunner::Results> ProcessRunner::runBinary() // if a plugin doesn't want the program to run. if (!m_core.beforeRun(m_sp.binary, m_sp.currentDirectory, m_sp.arguments, m_profileName, m_customOverwrite, m_forcedLibraries, - &m_sp.saveBindMountSource, &m_sp.saveBindMountTarget, - &m_sp.vfsBridgeIndexPath, &m_sp.vfsBridgeDataDir, - &m_sp.vfsBridgeMountPoint)) { + &m_sp.saveBindMountSource, &m_sp.saveBindMountTarget)) { return Error; } diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp index 8976632..4e71c55 100644 --- a/src/src/protonlauncher.cpp +++ b/src/src/protonlauncher.cpp @@ -7,8 +7,6 @@ #include <QByteArray> #include <QCoreApplication> #include <QDir> -#include <QDirIterator> -#include <QElapsedTimer> #include <QFile> #include <QFileInfo> #include <QProcess> @@ -17,9 +15,6 @@ #include <QSet> #include <log.h> -#include <fcntl.h> -#include <unistd.h> - namespace { // Restore the pre-launcher environment for child processes (Proton, Wine). @@ -78,355 +73,6 @@ void cleanFluorineEnv(QProcessEnvironment& env) env.value("LD_LIBRARY_PATH", "<unset>")); } -QString fluorineVfsPreloadPath() -{ - const QString libName = QStringLiteral("libfluorine_vfs_preload.so"); - const QString envLibDir = qEnvironmentVariable("MO2_LIBS_DIR"); - if (!envLibDir.isEmpty()) { - const QString candidate = QDir(envLibDir).filePath(libName); - if (QFileInfo::exists(candidate)) { - return candidate; - } - } - - const QString candidate = - QDir(QCoreApplication::applicationDirPath()).filePath( - QStringLiteral("lib/") + libName); - return QFileInfo::exists(candidate) ? candidate : QString(); -} - -void prependLdPreload(QProcessEnvironment& env, const QString& library) -{ - if (library.isEmpty()) { - return; - } - - const QString existing = env.value("LD_PRELOAD"); - env.insert("LD_PRELOAD", existing.isEmpty() ? library : library + ":" + existing); -} - -void prependWineDllOverride(QProcessEnvironment& env, const QString& override) -{ - if (override.isEmpty()) { - return; - } - - const QString existing = env.value("WINEDLLOVERRIDES"); - if (existing.split(';').contains(override)) { - return; - } - env.insert("WINEDLLOVERRIDES", - existing.isEmpty() ? override : override + ";" + existing); -} - -bool vfsBridgeIndexPresent(const QMap<QString, QString>& envVars) -{ - return envVars.contains("FLUORINE_VFS_INDEX") && - !envVars.value("FLUORINE_VFS_INDEX").isEmpty(); -} - -struct StagedProxy -{ - QString path; - QByteArray expectedBytes; - - explicit operator bool() const - { - return !path.isEmpty() && !expectedBytes.isEmpty(); - } -}; - -QByteArray readSmallFile(const QString& path) -{ - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - return {}; - } - return file.readAll(); -} - -void cleanupStagedProxy(const StagedProxy& proxy) -{ - if (!proxy) { - return; - } - - const QByteArray current = readSmallFile(proxy.path); - if (!current.isEmpty() && current == proxy.expectedBytes) { - if (QFile::remove(proxy.path)) { - MOBase::log::info("VFS HID proxy removed: {}", proxy.path); - } else { - MOBase::log::warn("VFS HID proxy cleanup failed: {}", proxy.path); - } - } -} - -StagedProxy stageVfsHidProxy(const QString& binary) -{ - const QString src = fluorineVfsHidProxyDllPath(); - const QByteArray srcBytes = readSmallFile(src); - if (src.isEmpty() || binary.isEmpty()) { - return {}; - } - if (srcBytes.isEmpty()) { - MOBase::log::warn("VFS HID proxy not staged: failed to read '{}'", src); - return {}; - } - - const QString gameDir = QFileInfo(binary).absolutePath(); - const QString dst = QDir(gameDir).filePath(QStringLiteral("hid.dll")); - const QFileInfo dstInfo(dst); - if (dstInfo.exists()) { - const QByteArray dstBytes = readSmallFile(dst); - if (srcBytes == dstBytes) { - return {dst, srcBytes}; - } - if (dstBytes.contains("fluorine_vfs")) { - if (!QFile::remove(dst)) { - MOBase::log::warn("VFS HID proxy update failed: could not remove '{}'", dst); - return {}; - } - } else { - MOBase::log::warn("VFS HID proxy not staged: '{}' already exists", dst); - return {}; - } - } - - if (!QFile::copy(src, dst)) { - MOBase::log::warn("VFS HID proxy copy failed: '{}' -> '{}'", src, dst); - return {}; - } - - MOBase::log::info("VFS HID proxy staged: {}", dst); - return {dst, srcBytes}; -} - -bool vfsBridgePreloadRequested() -{ - // Default OFF. The libc-side LD_PRELOAD bridge cannot intercept Wine - // NTDLL's directory reads (Wine emits inline `syscall` instructions or - // routes via ntdll-internal vDSO thunks; neither path resolves through - // glibc symbols). When dir synthesis was on it caused empty-dir hangs - // (>60s) and the canonicalizer overhead made even file-only redirect - // net-negative versus baseline (~43s vs ~25s). The replacement is a - // PE-side `fluorine_vfs.dll` injected via AppInit_DLLs (in progress). - // Set FLUORINE_ENABLE_VFS_PRELOAD=1 to opt back in for diagnostics. - const QString enable = qEnvironmentVariable("FLUORINE_ENABLE_VFS_PRELOAD").trimmed(); - return enable == "1" || - enable.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 || - enable.compare(QStringLiteral("yes"), Qt::CaseInsensitive) == 0; -} - -bool wineLoaderTraceRequested() -{ - const QString enable = qEnvironmentVariable("FLUORINE_WINE_LOADER_TRACE").trimmed(); - return enable == "1" || - enable.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0 || - enable.compare(QStringLiteral("yes"), Qt::CaseInsensitive) == 0 || - QFileInfo::exists(QStringLiteral("/tmp/fluorine_enable_wine_loader_trace")); -} - -bool vfsPrewarmRequested(const QMap<QString, QString>& envVars) -{ - QString enable = envVars.value(QStringLiteral("FLUORINE_VFS_PREWARM")).trimmed(); - if (enable.isEmpty()) { - enable = qEnvironmentVariable("FLUORINE_VFS_PREWARM").trimmed(); - } - - if (enable.isEmpty()) { - return true; - } - - return !(enable == "0" || - enable.compare(QStringLiteral("false"), Qt::CaseInsensitive) == 0 || - enable.compare(QStringLiteral("no"), Qt::CaseInsensitive) == 0); -} - -bool isBethesdaPrewarmPath(const QString& path) -{ - const QString lower = path.toLower(); - return lower.endsWith(QStringLiteral(".dll")) || - lower.endsWith(QStringLiteral(".esp")) || - lower.endsWith(QStringLiteral(".esm")) || - lower.endsWith(QStringLiteral(".esl")) || - lower.endsWith(QStringLiteral(".bsa")) || - lower.endsWith(QStringLiteral(".ba2")); -} - -void addPrewarmPath(QStringList& paths, QSet<QString>& seen, const QString& path) -{ - if (path.isEmpty() || !isBethesdaPrewarmPath(path)) { - return; - } - - const QFileInfo info(path); - if (!info.exists() || !info.isFile()) { - return; - } - - const QString clean = QDir::cleanPath(info.absoluteFilePath()); - if (!seen.contains(clean)) { - seen.insert(clean); - paths.append(clean); - } -} - -QString jsonStringField(const QByteArray& line, const char* name) -{ - const QByteArray needle = QByteArray("\"") + name + "\":\""; - const int start = line.indexOf(needle); - if (start < 0) { - return {}; - } - - QByteArray out; - bool escaped = false; - for (int i = start + needle.size(); i < line.size(); ++i) { - const char c = line.at(i); - if (escaped) { - switch (c) { - case '"': - case '\\': - case '/': - out.append(c); - break; - case 'b': - out.append('\b'); - break; - case 'f': - out.append('\f'); - break; - case 'n': - out.append('\n'); - break; - case 'r': - out.append('\r'); - break; - case 't': - out.append('\t'); - break; - default: - out.append(c); - break; - } - escaped = false; - continue; - } - if (c == '\\') { - escaped = true; - continue; - } - if (c == '"') { - return QString::fromUtf8(out); - } - out.append(c); - } - - return {}; -} - -void collectStockPrewarmPaths(QStringList& paths, QSet<QString>& seen, - const QString& binary) -{ - const QString gameDir = QFileInfo(binary).absolutePath(); - if (gameDir.isEmpty() || !QFileInfo(gameDir).isDir()) { - return; - } - - QDirIterator it(gameDir, QDir::Files | QDir::NoDotAndDotDot, - QDirIterator::Subdirectories); - while (it.hasNext()) { - addPrewarmPath(paths, seen, it.next()); - } -} - -void collectIndexPrewarmPaths(QStringList& paths, QSet<QString>& seen, - const QString& indexPath) -{ - QFile file(indexPath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - MOBase::log::warn("VFS prewarm skipped index paths: failed to open '{}'", - indexPath); - return; - } - - while (!file.atEnd()) { - const QByteArray line = file.readLine(); - if (!line.contains("\"record\":\"entry\"") || - !line.contains("\"type\":\"file\"")) { - continue; - } - - const QString realPath = jsonStringField(line, "real_path"); - if (realPath.startsWith('/')) { - addPrewarmPath(paths, seen, realPath); - } - } -} - -struct PrewarmStats -{ - int files = 0; - int failed = 0; - qint64 bytes = 0; -}; - -PrewarmStats prewarmFiles(const QStringList& paths, int begin, int end) -{ - PrewarmStats stats; - for (int i = begin; i < end; ++i) { - const QFileInfo info(paths.at(i)); - const QByteArray nativePath = QFile::encodeName(info.absoluteFilePath()); - const int fd = ::open(nativePath.constData(), O_RDONLY | O_CLOEXEC); - if (fd < 0) { - ++stats.failed; - continue; - } - -#ifdef POSIX_FADV_WILLNEED - if (::posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) != 0) { - ++stats.failed; - ::close(fd); - continue; - } -#endif - - ++stats.files; - stats.bytes += info.size(); - ::close(fd); - } - return stats; -} - -void prewarmVfsBridgeFiles(const QString& binary, const QString& indexPath) -{ - QElapsedTimer timer; - timer.start(); - - QStringList paths; - QSet<QString> seen; - collectStockPrewarmPaths(paths, seen, binary); - const int stockEnd = paths.size(); - collectIndexPrewarmPaths(paths, seen, indexPath); - - const PrewarmStats stock = prewarmFiles(paths, 0, stockEnd); - const PrewarmStats indexed = prewarmFiles(paths, stockEnd, paths.size()); - - const qint64 elapsedMs = timer.elapsed(); - MOBase::log::info( - "VFS prewarm: stock_files={} stock_bytes={} index_files={} " - "index_bytes={} failed={} elapsed_ms={}", - stock.files, stock.bytes, indexed.files, indexed.bytes, - stock.failed + indexed.failed, elapsedMs); - std::fprintf(stderr, - "[Fluorine] VFS prewarm: stock_files=%d stock_bytes=%lld " - "index_files=%d index_bytes=%lld failed=%d elapsed_ms=%lld\n", - stock.files, static_cast<long long>(stock.bytes), - indexed.files, static_cast<long long>(indexed.bytes), - stock.failed + indexed.failed, - static_cast<long long>(elapsedMs)); -} - QString decodeMountInfoPath(const QByteArray& encoded) { QByteArray decoded; @@ -811,8 +457,7 @@ void wrapInTerminal(QString& program, QStringList& arguments) bool startWithEnv(const QString& program, const QStringList& arguments, const QString& workingDir, - const QProcessEnvironment& environment, qint64& pid, - const StagedProxy& stagedProxy) + const QProcessEnvironment& environment, qint64& pid) { auto* process = new QProcess(); process->setProgram(program); @@ -825,28 +470,10 @@ bool startWithEnv(const QString& program, const QStringList& arguments, process->setProcessEnvironment(environment); process->setProcessChannelMode(QProcess::ForwardedOutputChannel); - QFile* wineTraceLog = nullptr; - const QString wineTracePath = - environment.value(QStringLiteral("FLUORINE_WINE_LOADER_TRACE_LOG")); - if (!wineTracePath.isEmpty()) { - wineTraceLog = new QFile(wineTracePath, process); - if (!wineTraceLog->open(QIODevice::WriteOnly | QIODevice::Truncate)) { - MOBase::log::warn("Wine loader trace log could not be opened: {}", - wineTracePath); - wineTraceLog = nullptr; - } else { - MOBase::log::info("Wine loader trace log: {}", wineTracePath); - } - } - // Filter noisy Wine/Proton stderr (GStreamer warnings, etc.) while // forwarding everything else to our stderr. - QObject::connect(process, &QProcess::readyReadStandardError, process, [process, wineTraceLog]() { + QObject::connect(process, &QProcess::readyReadStandardError, process, [process]() { const QByteArray data = process->readAllStandardError(); - if (wineTraceLog != nullptr && wineTraceLog->isOpen()) { - wineTraceLog->write(data); - wineTraceLog->flush(); - } for (const QByteArray& line : data.split('\n')) { if (line.isEmpty()) continue; @@ -862,9 +489,6 @@ bool startWithEnv(const QString& program, const QStringList& arguments, QObject::connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), process, &QProcess::deleteLater); - QObject::connect(process, - QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - process, [stagedProxy]() { cleanupStagedProxy(stagedProxy); }); process->start(); if (!process->waitForStarted(5000)) { @@ -1101,16 +725,6 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const QStringList pressureVesselImportantPaths; pressureVesselImportantPaths << m_binary << m_workingDir << m_prefixPath << m_bindMountSource << m_bindMountTarget; - const bool useVfsBridgeIndex = vfsBridgeIndexPresent(m_envVars); - const StagedProxy vfsHidProxy = - useVfsBridgeIndex ? stageVfsHidProxy(m_binary) : StagedProxy{}; - const bool useVfsBridgePreload = - vfsBridgePreloadRequested() && useVfsBridgeIndex; - const QString vfsBridgePreload = - useVfsBridgePreload ? fluorineVfsPreloadPath() : QString(); - if (!vfsBridgePreload.isEmpty()) { - pressureVesselImportantPaths << vfsBridgePreload; - } // If SLR is enabled, wrap the whole proton invocation inside the // pressure-vessel container provided by SteamLinuxRuntime_sniper. @@ -1378,26 +992,6 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const for (auto it = m_envVars.cbegin(); it != m_envVars.cend(); ++it) { env.insert(it.key(), it.value()); } - if (useVfsBridgeIndex && wineLoaderTraceRequested()) { - const QString tracePath = - QStringLiteral("/tmp/fluorine_wine_loader_trace.log"); - QFile::remove(tracePath); - env.insert("WINEDEBUG", "+timestamp,+pid,+tid,+loaddll"); - env.insert("FLUORINE_WINE_LOADER_TRACE_LOG", tracePath); - MOBase::log::info("Wine loader tracing enabled: WINEDEBUG='{}', log='{}'", - env.value("WINEDEBUG"), tracePath); - } - if (vfsHidProxy) { - prependWineDllOverride(env, QStringLiteral("hid=n,b")); - } - if (useVfsBridgePreload && !vfsBridgePreload.isEmpty()) { - prependLdPreload(env, vfsBridgePreload); - env.insert("FLUORINE_VFS_PRELOAD_STATS", "1"); - MOBase::log::info("VFS bridge preload enabled: {}", vfsBridgePreload); - } else if (useVfsBridgePreload) { - MOBase::log::warn("VFS bridge index is present but libfluorine_vfs_preload.so " - "was not found; launching without preload acceleration"); - } if (m_useSLR) { appendPressureVesselFilesystems( @@ -1441,15 +1035,11 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const env.insert("PWD", m_workingDir); } - if (useVfsBridgeIndex && vfsPrewarmRequested(m_envVars)) { - prewarmVfsBridgeFiles(m_binary, m_envVars.value("FLUORINE_VFS_INDEX")); - } - if (m_useTerminal) { wrapInTerminal(program, arguments); } - return startWithEnv(program, arguments, m_workingDir, env, pid, vfsHidProxy); + return startWithEnv(program, arguments, m_workingDir, env, pid); } bool ProtonLauncher::launchDirect(qint64& pid) const @@ -1471,42 +1061,12 @@ bool ProtonLauncher::launchDirect(qint64& pid) const for (auto it = m_envVars.cbegin(); it != m_envVars.cend(); ++it) { env.insert(it.key(), it.value()); } - const bool useVfsBridgeIndex = vfsBridgeIndexPresent(m_envVars); - const StagedProxy vfsHidProxy = - useVfsBridgeIndex ? stageVfsHidProxy(m_binary) : StagedProxy{}; - if (useVfsBridgeIndex && wineLoaderTraceRequested()) { - const QString tracePath = - QStringLiteral("/tmp/fluorine_wine_loader_trace.log"); - QFile::remove(tracePath); - env.insert("WINEDEBUG", "+timestamp,+pid,+tid,+loaddll"); - env.insert("FLUORINE_WINE_LOADER_TRACE_LOG", tracePath); - MOBase::log::info("Wine loader tracing enabled: WINEDEBUG='{}', log='{}'", - env.value("WINEDEBUG"), tracePath); - } - if (vfsHidProxy) { - prependWineDllOverride(env, QStringLiteral("hid=n,b")); - } - if (vfsBridgePreloadRequested() && useVfsBridgeIndex) { - const QString vfsBridgePreload = fluorineVfsPreloadPath(); - if (!vfsBridgePreload.isEmpty()) { - prependLdPreload(env, vfsBridgePreload); - env.insert("FLUORINE_VFS_PRELOAD_STATS", "1"); - MOBase::log::info("VFS bridge preload enabled: {}", vfsBridgePreload); - } else { - MOBase::log::warn("VFS bridge index is present but libfluorine_vfs_preload.so " - "was not found; launching without preload acceleration"); - } - } - - if (useVfsBridgeIndex && vfsPrewarmRequested(m_envVars)) { - prewarmVfsBridgeFiles(m_binary, m_envVars.value("FLUORINE_VFS_INDEX")); - } if (m_useTerminal) { wrapInTerminal(program, arguments); } - return startWithEnv(program, arguments, m_workingDir, env, pid, vfsHidProxy); + return startWithEnv(program, arguments, m_workingDir, env, pid); } bool ProtonLauncher::ensureSteamRunning() diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp index f7a4248..76d4e0e 100644 --- a/src/src/spawn.cpp +++ b/src/src/spawn.cpp @@ -476,22 +476,6 @@ int spawn(const SpawnParameters& sp, pid_t& processId) MOBase::log::info("Proton disabled for this executable, launching directly"); } - // PE-side VFS bridge is per-executable opt-in. Without these env - // vars the staged fluorine_vfs.dll loads but its hook_worker bails - // out at load_index() (FLUORINE_VFS_INDEX unset) and installs zero - // hooks — equivalent to the bridge being disabled. - if (sp.useVfsBridge) { - if (!sp.vfsBridgeIndexPath.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_INDEX", sp.vfsBridgeIndexPath); - } - if (!sp.vfsBridgeDataDir.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_DATA_DIR", sp.vfsBridgeDataDir); - } - if (!sp.vfsBridgeMountPoint.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_MOUNT", sp.vfsBridgeMountPoint); - } - } - launcher.setUseTerminal(sp.useTerminal); const auto [ok, pid] = launcher.launch(); diff --git a/src/src/spawn.h b/src/src/spawn.h index 010edb9..40207c3 100644 --- a/src/src/spawn.h +++ b/src/src/spawn.h @@ -53,7 +53,6 @@ struct SpawnParameters bool hooked = false;
bool useProton = true;
bool useTerminal = false;
- bool useVfsBridge = false;
int stdOut = -1;
int stdErr = -1;
// When both are set and unprivileged user namespaces are available,
@@ -63,9 +62,6 @@ struct SpawnParameters // without symlinks, which Wine can accidentally replace.
QString saveBindMountSource;
QString saveBindMountTarget;
- QString vfsBridgeIndexPath;
- QString vfsBridgeDataDir;
- QString vfsBridgeMountPoint;
};
bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory,
diff --git a/src/src/vfs/fluorine_vfs_hooks.c b/src/src/vfs/fluorine_vfs_hooks.c deleted file mode 100644 index 366963d..0000000 --- a/src/src/vfs/fluorine_vfs_hooks.c +++ /dev/null @@ -1,2729 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <stdint.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <MinHook.h> - -typedef LONG NTSTATUS; - -typedef struct _IO_STATUS_BLOCK_FL -{ - union { - NTSTATUS Status; - PVOID Pointer; - } u; - ULONG_PTR Information; -} IO_STATUS_BLOCK_FL, *PIO_STATUS_BLOCK_FL; - -typedef struct _UNICODE_STRING_FL -{ - USHORT Length; - USHORT MaximumLength; - PWSTR Buffer; -} UNICODE_STRING_FL, *PUNICODE_STRING_FL; - -typedef struct _OBJECT_ATTRIBUTES_FL -{ - ULONG Length; - HANDLE RootDirectory; - PUNICODE_STRING_FL ObjectName; - ULONG Attributes; - PVOID SecurityDescriptor; - PVOID SecurityQualityOfService; -} OBJECT_ATTRIBUTES_FL, *POBJECT_ATTRIBUTES_FL; - -typedef VOID(NTAPI* PIO_APC_ROUTINE_FL)(PVOID, PIO_STATUS_BLOCK_FL, ULONG); - -typedef NTSTATUS(NTAPI* NtCreateFile_t)(PHANDLE, ACCESS_MASK, - POBJECT_ATTRIBUTES_FL, - PIO_STATUS_BLOCK_FL, PLARGE_INTEGER, - ULONG, ULONG, ULONG, ULONG, PVOID, - ULONG); -typedef NTSTATUS(NTAPI* NtOpenFile_t)(PHANDLE, ACCESS_MASK, - POBJECT_ATTRIBUTES_FL, - PIO_STATUS_BLOCK_FL, ULONG, ULONG); -typedef NTSTATUS(NTAPI* NtClose_t)(HANDLE); -typedef NTSTATUS(NTAPI* NtQueryDirectoryFile_t)( - HANDLE, HANDLE, PIO_APC_ROUTINE_FL, PVOID, PIO_STATUS_BLOCK_FL, PVOID, - ULONG, ULONG, BOOLEAN, PUNICODE_STRING_FL, BOOLEAN); -typedef NTSTATUS(NTAPI* NtQueryDirectoryFileEx_t)( - HANDLE, HANDLE, PIO_APC_ROUTINE_FL, PVOID, PIO_STATUS_BLOCK_FL, PVOID, - ULONG, ULONG, ULONG, PUNICODE_STRING_FL); -typedef NTSTATUS(NTAPI* NtQueryAttributesFile_t)(POBJECT_ATTRIBUTES_FL, - PVOID); -typedef NTSTATUS(NTAPI* NtQueryFullAttributesFile_t)(POBJECT_ATTRIBUTES_FL, - PVOID); -typedef NTSTATUS(NTAPI* NtReadFile_t)(HANDLE, HANDLE, PIO_APC_ROUTINE_FL, - PVOID, PIO_STATUS_BLOCK_FL, PVOID, - ULONG, PLARGE_INTEGER, PULONG); -typedef NTSTATUS(NTAPI* NtSetInformationFile_t)(HANDLE, - PIO_STATUS_BLOCK_FL, PVOID, - ULONG, ULONG); -typedef NTSTATUS(NTAPI* NtQueryInformationFile_t)(HANDLE, - PIO_STATUS_BLOCK_FL, - PVOID, ULONG, ULONG); -typedef BOOL(WINAPI* GetFileAttributesExW_t)(LPCWSTR, GET_FILEEX_INFO_LEVELS, - LPVOID); -typedef BOOL(WINAPI* GetFileAttributesExA_t)(LPCSTR, GET_FILEEX_INFO_LEVELS, - LPVOID); - -#ifndef STATUS_SUCCESS -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#endif -#ifndef STATUS_NO_MORE_FILES -#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L) -#endif -#ifndef STATUS_BUFFER_OVERFLOW -#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) -#endif -#ifndef STATUS_INFO_LENGTH_MISMATCH -#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) -#endif -#ifndef STATUS_OBJECT_NAME_NOT_FOUND -#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) -#endif -#ifndef STATUS_END_OF_FILE -#define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) -#endif - -#define FL_FilePositionInformation 14u -#define FL_FileStandardInformation 5u -#define FL_FileEndOfFileInformation 20u - -typedef struct _FL_FILE_POSITION_INFORMATION { - LARGE_INTEGER CurrentByteOffset; -} FL_FILE_POSITION_INFORMATION; - -typedef struct _FL_FILE_STANDARD_INFORMATION { - LARGE_INTEGER AllocationSize; - LARGE_INTEGER EndOfFile; - ULONG NumberOfLinks; - BOOLEAN DeletePending; - BOOLEAN Directory; -} FL_FILE_STANDARD_INFORMATION; - -#define FL_FileDirectoryInformation 1u -#define FL_FileFullDirectoryInformation 2u -#define FL_FileBothDirectoryInformation 3u -#define FL_FileNamesInformation 12u -#define FL_FileIdBothDirectoryInformation 37u -#define FL_FileIdFullDirectoryInformation 38u -#define FL_SL_RESTART_SCAN 0x00000001u -#define FL_SL_RETURN_SINGLE_ENTRY 0x00000002u - -typedef struct _FL_FILE_DIRECTORY_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - WCHAR FileName[1]; -} FL_FILE_DIRECTORY_INFORMATION; - -typedef struct _FL_FILE_FULL_DIR_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - WCHAR FileName[1]; -} FL_FILE_FULL_DIR_INFORMATION; - -typedef struct _FL_FILE_BOTH_DIR_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - WCHAR FileName[1]; -} FL_FILE_BOTH_DIR_INFORMATION; - -typedef struct _FL_FILE_ID_BOTH_DIR_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - CCHAR ShortNameLength; - WCHAR ShortName[12]; - LARGE_INTEGER FileId; - WCHAR FileName[1]; -} FL_FILE_ID_BOTH_DIR_INFORMATION; - -typedef struct _FL_FILE_ID_FULL_DIR_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER EndOfFile; - LARGE_INTEGER AllocationSize; - ULONG FileAttributes; - ULONG FileNameLength; - ULONG EaSize; - LARGE_INTEGER FileId; - WCHAR FileName[1]; -} FL_FILE_ID_FULL_DIR_INFORMATION; - -typedef struct _FL_FILE_NAMES_INFORMATION { - ULONG NextEntryOffset; - ULONG FileIndex; - ULONG FileNameLength; - WCHAR FileName[1]; -} FL_FILE_NAMES_INFORMATION; - -typedef struct _FL_FILE_BASIC_INFORMATION { - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - ULONG FileAttributes; -} FL_FILE_BASIC_INFORMATION; - -typedef struct _FL_FILE_NETWORK_OPEN_INFORMATION { - LARGE_INTEGER CreationTime; - LARGE_INTEGER LastAccessTime; - LARGE_INTEGER LastWriteTime; - LARGE_INTEGER ChangeTime; - LARGE_INTEGER AllocationSize; - LARGE_INTEGER EndOfFile; - ULONG FileAttributes; -} FL_FILE_NETWORK_OPEN_INFORMATION; - -static NtCreateFile_t Real_NtCreateFile = NULL; -static NtOpenFile_t Real_NtOpenFile = NULL; -static NtClose_t Real_NtClose = NULL; -static NtQueryDirectoryFile_t Real_NtQueryDirectoryFile = NULL; -static NtQueryDirectoryFileEx_t Real_NtQueryDirectoryFileEx = NULL; -static NtQueryAttributesFile_t Real_NtQueryAttributesFile = NULL; -static NtQueryFullAttributesFile_t Real_NtQueryFullAttributesFile = NULL; -static NtReadFile_t Real_NtReadFile = NULL; -static NtSetInformationFile_t Real_NtSetInformationFile = NULL; -static NtQueryInformationFile_t Real_NtQueryInformationFile = NULL; -static GetFileAttributesExW_t Real_GetFileAttributesExW = NULL; -static GetFileAttributesExA_t Real_GetFileAttributesExA = NULL; - -static volatile LONG g_started = 0; -static volatile LONG g_installed = 0; -static volatile LONG g_stats_started = 0; -static volatile LONG g_first_create_logged = 0; -static volatile LONG g_first_open_logged = 0; -static volatile LONG g_first_mmap_logged = 0; -static ULONGLONG g_phase_start_tick = 0; -static volatile LONG64 g_nt_create = 0; -static volatile LONG64 g_nt_open = 0; -static volatile LONG64 g_nt_close = 0; -static volatile LONG64 g_nt_qdf = 0; -static volatile LONG64 g_nt_qdfex = 0; -static volatile LONG64 g_nt_qattr = 0; -static volatile LONG64 g_nt_qfullattr = 0; -static volatile LONG64 g_redirect_file = 0; -static volatile LONG64 g_redirect_miss = 0; -static volatile LONG64 g_redirect_skip = 0; -static volatile LONG64 g_redirect_dir = 0; -static volatile LONG64 g_dir_query_served = 0; -static volatile LONG64 g_dir_query_empty = 0; -static volatile LONG64 g_dir_query_bypass = 0; -static volatile LONG64 g_attr_hit = 0; -static volatile LONG64 g_attr_miss = 0; -static volatile LONG64 g_attr_neg = 0; -static volatile LONG64 g_attr_passthru = 0; -static volatile LONG64 g_attr_cache_hit = 0; -static volatile LONG64 g_attr_cache_insert = 0; -static volatile LONG64 g_attr_cache_full = 0; -static volatile LONG64 g_k32_gfaexw = 0; -static volatile LONG64 g_k32_gfaexa = 0; -static volatile LONG64 g_k32_attr_hit = 0; -static volatile LONG64 g_k32_cache_hit = 0; -static volatile LONG64 g_k32_passthru = 0; -static volatile LONG64 g_k32_neg = 0; -static volatile LONG64 g_k32_fast_path = 0; -static volatile LONG64 g_k32_full_path = 0; -static volatile LONG64 g_live_insert = 0; -static volatile LONG64 g_open_neg = 0; -static volatile LONG64 g_mmap_attached = 0; -static volatile LONG64 g_mmap_skip_size = 0; -static volatile LONG64 g_mmap_skip_zero = 0; -static volatile LONG64 g_mmap_map_failed = 0; -static volatile LONG64 g_mmap_view_failed = 0; -static volatile LONG64 g_mmap_reads = 0; -static volatile LONG64 g_mmap_bytes = 0; -static volatile LONG64 g_mmap_passthru = 0; -static volatile LONG64 g_mmap_detached = 0; - -typedef struct VfsEntry -{ - char* key; - WCHAR* real_nt; - ULONG attrs; - unsigned long long size; - FILETIME mtime; -} VfsEntry; - -typedef struct ChildEntry -{ - char* key; - WCHAR* name; - ULONG attrs; - unsigned long long size; - FILETIME mtime; -} ChildEntry; - -typedef struct DirBucket -{ - char* key; - ChildEntry* children; - size_t child_count; - size_t child_cap; - struct DirBucket* next; -} DirBucket; - -typedef struct DirHandleState -{ - HANDLE handle; - DirBucket* bucket; - size_t cursor; - WCHAR* filter; - size_t filter_len; - int filter_set; -} DirHandleState; - -static VfsEntry* g_entries = NULL; -static size_t g_entry_count = 0; -static DirBucket** g_dir_hash = NULL; -static size_t g_dir_count = 0; -static size_t g_child_count = 0; -static DirHandleState* g_dir_handles = NULL; -static size_t g_dir_handle_count = 0; -static size_t g_dir_handle_cap = 0; -static CRITICAL_SECTION g_dir_lock; -static int g_dir_lock_ready = 0; -static HANDLE g_dummy_dir = INVALID_HANDLE_VALUE; -static char* g_mount = NULL; -/* Precomputed mount prefixes for the fast scope gate. Built once in - * load_index() after g_mount is normalized. The gate keeps the bridge from - * touching any file outside the game's data dir: every probe to a system - * DLL, registry-backed object, or unrelated drive bypasses the hook entirely - * and goes straight to the real syscall. */ -static WCHAR* g_mount_w_nt = NULL; /* "\\??\\Z:\\<mount>" lowercase */ -static size_t g_mount_w_nt_chars = 0; -static WCHAR* g_mount_w_dosq = NULL; /* "\\\\?\\Z:\\<mount>" lowercase */ -static size_t g_mount_w_dosq_chars = 0; -static WCHAR* g_mount_w_bare = NULL; /* "Z:\\<mount>" lowercase */ -static size_t g_mount_w_bare_chars = 0; -static char* g_mount_a_bare = NULL; /* "z:\\<mount>" lowercase ANSI */ -static size_t g_mount_a_bare_len = 0; -static char* g_mount_a_dosq = NULL; /* "\\\\?\\z:\\<mount>" lowercase ANSI */ -static size_t g_mount_a_dosq_len = 0; -static volatile LONG g_index_ready = 0; -static __thread int t_inside_redirect = 0; -static __thread WCHAR t_redirect_path[32768]; - -static DWORD copy_str(char* dst, DWORD pos, DWORD cap, const char* src); - -static char ascii_lower(char c) -{ - return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c; -} - -static int ascii_eq_ci(const char* a, const char* b) -{ - while (*a && *b) { - char ca = ascii_lower(*a); - char cb = ascii_lower(*b); - if (ca != cb) return 0; - ++a; - ++b; - } - return *a == '\0' && *b == '\0'; -} - -static char* heap_strndup(const char* s, size_t n) -{ - char* out = (char*)malloc(n + 1); - if (out == NULL) return NULL; - memcpy(out, s, n); - out[n] = '\0'; - return out; -} - -static char* normalize_utf8_path(char* s) -{ - char* r = s; - char* w = s; - while (*r == '/' || *r == '\\') ++r; - while (*r) { - char c = *r++; - if (c == '\\') c = '/'; - *w++ = ascii_lower(c); - } - while (w > s && w[-1] == '/') --w; - *w = '\0'; - return s; -} - -static char* json_string_field(const char* line, const char* key) -{ - char needle[96]; - DWORD pos = 0; - pos = copy_str(needle, pos, sizeof(needle), "\""); - pos = copy_str(needle, pos, sizeof(needle), key); - pos = copy_str(needle, pos, sizeof(needle), "\":"); - needle[pos] = '\0'; - - const char* p = strstr(line, needle); - if (p == NULL) return NULL; - p = strchr(p + strlen(needle), '"'); - if (p == NULL) return NULL; - ++p; - - char* out = (char*)malloc(strlen(p) + 1); - if (out == NULL) return NULL; - size_t n = 0; - while (*p) { - char c = *p++; - if (c == '"') { - out[n] = '\0'; - return out; - } - if (c == '\\' && *p) { - char e = *p++; - switch (e) { - case '"': c = '"'; break; - case '\\': c = '\\'; break; - case '/': c = '/'; break; - case 'b': c = '\b'; break; - case 'f': c = '\f'; break; - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - default: c = e; break; - } - } - out[n++] = c; - } - free(out); - return NULL; -} - -static WCHAR* unix_path_to_nt_wide(const char* path) -{ - if (path == NULL || path[0] != '/') return NULL; - size_t len = strlen(path); - char* nt = (char*)malloc(len + 8); - if (nt == NULL) return NULL; - strcpy(nt, "\\??\\Z:"); - strcpy(nt + 6, path); - for (char* p = nt; *p; ++p) { - if (*p == '/') *p = '\\'; - } - - int wlen = MultiByteToWideChar(CP_UTF8, 0, nt, -1, NULL, 0); - if (wlen <= 0) { - free(nt); - return NULL; - } - WCHAR* out = (WCHAR*)malloc((size_t)wlen * sizeof(WCHAR)); - if (out == NULL) { - free(nt); - return NULL; - } - MultiByteToWideChar(CP_UTF8, 0, nt, -1, out, wlen); - free(nt); - return out; -} - -/* --- Mount-scope fast gate -------------------------------------------------- - * - * The bridge only ever has knowledge of files inside the FUSE mount (i.e. the - * game's Data dir). Any probe to a path outside that subtree must be invisible - * to our hooks: pass straight through to Wine, no normalization, no cache - * lookup, no possible chance of a wrong answer. - * - * To make that cheap, we precompute the mount prefix in the three forms that - * Wine/games commonly produce, lowercased, and do a byte/wide prefix compare - * directly on the input the kernel/k32 hook receives. Out-of-mount paths fail - * the compare in a handful of characters and pass through without any of the - * UTF-8/slash/lowercase machinery in nt_path_to_key(). - */ - -static WCHAR ascii_lower_w(WCHAR c) -{ - return (c >= L'A' && c <= L'Z') ? (WCHAR)(c + 32) : c; -} - -static int wide_match_prefix(const WCHAR* path, size_t path_chars, - const WCHAR* prefix, size_t prefix_chars) -{ - if (prefix == NULL || prefix_chars == 0) return 0; - if (path_chars < prefix_chars) return 0; - for (size_t i = 0; i < prefix_chars; ++i) { - if (ascii_lower_w(path[i]) != prefix[i]) return 0; - } - if (path_chars == prefix_chars) return 1; - WCHAR n = path[prefix_chars]; - return n == L'\\' || n == L'/'; -} - -static int byte_match_prefix(const char* path, size_t path_len, - const char* prefix, size_t prefix_len) -{ - if (prefix == NULL || prefix_len == 0) return 0; - if (path_len < prefix_len) return 0; - for (size_t i = 0; i < prefix_len; ++i) { - if (ascii_lower(path[i]) != prefix[i]) return 0; - } - if (path_len == prefix_len) return 1; - char n = path[prefix_len]; - return n == '\\' || n == '/'; -} - -static void build_mount_prefixes(void) -{ - if (g_mount == NULL) return; - size_t mlen = strlen(g_mount); - if (mlen == 0) return; - - /* g_mount is lowercase, forward-slash, no leading or trailing slash. - * Build "z:\\<mount-with-backslashes>" in ANSI lowercase first. */ - size_t bare_len = 3 + mlen; /* "z:\\" + mlen */ - char* a_bare = (char*)malloc(bare_len + 1); - if (a_bare == NULL) return; - a_bare[0] = 'z'; - a_bare[1] = ':'; - a_bare[2] = '\\'; - for (size_t i = 0; i < mlen; ++i) { - char c = g_mount[i]; - a_bare[3 + i] = (c == '/') ? '\\' : c; - } - a_bare[bare_len] = '\0'; - g_mount_a_bare = a_bare; - g_mount_a_bare_len = bare_len; - - /* "\\\\?\\z:\\<...>" */ - size_t dosq_len = 4 + bare_len; - char* a_dosq = (char*)malloc(dosq_len + 1); - if (a_dosq != NULL) { - a_dosq[0] = '\\'; - a_dosq[1] = '\\'; - a_dosq[2] = '?'; - a_dosq[3] = '\\'; - memcpy(a_dosq + 4, a_bare, bare_len); - a_dosq[dosq_len] = '\0'; - g_mount_a_dosq = a_dosq; - g_mount_a_dosq_len = dosq_len; - } - - /* Wide bare. */ - WCHAR* w_bare = (WCHAR*)malloc((bare_len + 1) * sizeof(WCHAR)); - if (w_bare == NULL) return; - for (size_t i = 0; i < bare_len; ++i) { - w_bare[i] = (WCHAR)(unsigned char)a_bare[i]; - } - w_bare[bare_len] = 0; - g_mount_w_bare = w_bare; - g_mount_w_bare_chars = bare_len; - - /* "\\??\\Z:\\<...>" wide */ - size_t nt_chars = 4 + bare_len; - WCHAR* w_nt = (WCHAR*)malloc((nt_chars + 1) * sizeof(WCHAR)); - if (w_nt != NULL) { - w_nt[0] = L'\\'; - w_nt[1] = L'?'; - w_nt[2] = L'?'; - w_nt[3] = L'\\'; - for (size_t i = 0; i < bare_len; ++i) w_nt[4 + i] = w_bare[i]; - w_nt[nt_chars] = 0; - g_mount_w_nt = w_nt; - g_mount_w_nt_chars = nt_chars; - } - - /* "\\\\?\\Z:\\<...>" wide */ - size_t dosq_chars = 4 + bare_len; - WCHAR* w_dosq = (WCHAR*)malloc((dosq_chars + 1) * sizeof(WCHAR)); - if (w_dosq != NULL) { - w_dosq[0] = L'\\'; - w_dosq[1] = L'\\'; - w_dosq[2] = L'?'; - w_dosq[3] = L'\\'; - for (size_t i = 0; i < bare_len; ++i) w_dosq[4 + i] = w_bare[i]; - w_dosq[dosq_chars] = 0; - g_mount_w_dosq = w_dosq; - g_mount_w_dosq_chars = dosq_chars; - } -} - -static int us_under_mount(PUNICODE_STRING_FL us) -{ - if (us == NULL || us->Buffer == NULL) return 0; - size_t chars = (size_t)(us->Length / sizeof(WCHAR)); - return wide_match_prefix(us->Buffer, chars, g_mount_w_nt, - g_mount_w_nt_chars) || - wide_match_prefix(us->Buffer, chars, g_mount_w_dosq, - g_mount_w_dosq_chars) || - wide_match_prefix(us->Buffer, chars, g_mount_w_bare, - g_mount_w_bare_chars); -} - -static int wide_under_mount(LPCWSTR path) -{ - if (path == NULL) return 0; - size_t chars = 0; - while (path[chars] != 0) ++chars; - return wide_match_prefix(path, chars, g_mount_w_nt, g_mount_w_nt_chars) || - wide_match_prefix(path, chars, g_mount_w_dosq, - g_mount_w_dosq_chars) || - wide_match_prefix(path, chars, g_mount_w_bare, - g_mount_w_bare_chars); -} - -static int ansi_under_mount(LPCSTR path) -{ - if (path == NULL) return 0; - size_t plen = strlen(path); - return byte_match_prefix(path, plen, g_mount_a_bare, - g_mount_a_bare_len) || - byte_match_prefix(path, plen, g_mount_a_dosq, - g_mount_a_dosq_len); -} - -static WCHAR* utf8_to_wide_n(const char* s, size_t n) -{ - int wlen = MultiByteToWideChar(CP_UTF8, 0, s, (int)n, NULL, 0); - if (wlen <= 0) return NULL; - WCHAR* out = (WCHAR*)malloc(((size_t)wlen + 1) * sizeof(WCHAR)); - if (out == NULL) return NULL; - MultiByteToWideChar(CP_UTF8, 0, s, (int)n, out, wlen); - out[wlen] = 0; - return out; -} - -static unsigned long long json_u64_field(const char* line, const char* key) -{ - char needle[96]; - DWORD pos = 0; - pos = copy_str(needle, pos, sizeof(needle), "\""); - pos = copy_str(needle, pos, sizeof(needle), key); - pos = copy_str(needle, pos, sizeof(needle), "\":"); - needle[pos] = '\0'; - - const char* p = strstr(line, needle); - if (p == NULL) return 0; - p += strlen(needle); - while (*p == ' ') ++p; - unsigned long long v = 0; - while (*p >= '0' && *p <= '9') { - v = (v * 10ULL) + (unsigned long long)(*p - '0'); - ++p; - } - return v; -} - -static FILETIME filetime_from_unix_ns(unsigned long long ns) -{ - unsigned long long ft = 116444736000000000ULL + (ns / 100ULL); - FILETIME out; - out.dwLowDateTime = (DWORD)(ft & 0xffffffffULL); - out.dwHighDateTime = (DWORD)(ft >> 32); - return out; -} - -static unsigned long hash_str(const char* s) -{ - unsigned long h = 2166136261u; - while (*s) { - h ^= (unsigned char)*s++; - h *= 16777619u; - } - return h; -} - -static DirBucket* get_dir_bucket(const char* key, int create) -{ - if (g_dir_hash == NULL) { - if (!create) return NULL; - g_dir_hash = (DirBucket**)calloc(65536, sizeof(DirBucket*)); - if (g_dir_hash == NULL) return NULL; - } - unsigned long slot = hash_str(key) & 65535u; - for (DirBucket* b = g_dir_hash[slot]; b != NULL; b = b->next) { - if (strcmp(b->key, key) == 0) return b; - } - if (!create) return NULL; - - DirBucket* b = (DirBucket*)calloc(1, sizeof(DirBucket)); - if (b == NULL) return NULL; - b->key = heap_strndup(key, strlen(key)); - if (b->key == NULL) { - free(b); - return NULL; - } - b->next = g_dir_hash[slot]; - g_dir_hash[slot] = b; - ++g_dir_count; - return b; -} - -static int child_cmp(const void* a, const void* b) -{ - const ChildEntry* ca = (const ChildEntry*)a; - const ChildEntry* cb = (const ChildEntry*)b; - return strcmp(ca->key, cb->key); -} - -static void sort_dir_children(void) -{ - if (g_dir_hash == NULL) return; - for (size_t i = 0; i < 65536; ++i) { - for (DirBucket* b = g_dir_hash[i]; b != NULL; b = b->next) { - if (b->child_count > 1) { - qsort(b->children, b->child_count, sizeof(ChildEntry), child_cmp); - } - } - } -} - -static int bucket_has_child(DirBucket* b, const char* child_key) -{ - for (size_t i = 0; i < b->child_count; ++i) { - if (strcmp(b->children[i].key, child_key) == 0) return 1; - } - return 0; -} - -static int add_child(const char* parent_key, const char* child_key, - const char* name, size_t name_len, ULONG attrs, - unsigned long long size, FILETIME mtime) -{ - DirBucket* b = get_dir_bucket(parent_key, 1); - if (b == NULL) return 0; - if (bucket_has_child(b, child_key)) return 1; - - if (b->child_count == b->child_cap) { - size_t next_cap = b->child_cap ? b->child_cap * 2 : 8; - ChildEntry* next = - (ChildEntry*)realloc(b->children, next_cap * sizeof(ChildEntry)); - if (next == NULL) return 0; - b->children = next; - b->child_cap = next_cap; - } - - ChildEntry* e = &b->children[b->child_count]; - memset(e, 0, sizeof(*e)); - e->key = heap_strndup(child_key, strlen(child_key)); - e->name = utf8_to_wide_n(name, name_len); - if (e->key == NULL || e->name == NULL) { - if (e->key != NULL) free(e->key); - if (e->name != NULL) free(e->name); - memset(e, 0, sizeof(*e)); - return 0; - } - e->attrs = attrs; - e->size = size; - e->mtime = mtime; - ++b->child_count; - ++g_child_count; - return 1; -} - -static void append_segment_key(char* dst, size_t cap, const char* parent, - const char* seg) -{ - if (parent[0] != '\0') { - snprintf(dst, cap, "%s/%s", parent, seg); - } else { - snprintf(dst, cap, "%s", seg); - } - dst[cap - 1] = '\0'; -} - -static void add_path_to_dirs(const char* original_path, - unsigned long long size, - FILETIME mtime) -{ - char parent[2048]; - parent[0] = '\0'; - get_dir_bucket("", 1); - - const char* p = original_path; - while (*p == '/' || *p == '\\') ++p; - while (*p) { - const char* start = p; - while (*p && *p != '/' && *p != '\\') ++p; - size_t n = (size_t)(p - start); - while (*p == '/' || *p == '\\') ++p; - if (n == 0) continue; - - char* seg = heap_strndup(start, n); - if (seg == NULL) return; - normalize_utf8_path(seg); - - char child_key[2048]; - append_segment_key(child_key, sizeof(child_key), parent, seg); - int is_file = (*p == '\0'); - add_child(parent, seg, start, n, - is_file ? FILE_ATTRIBUTE_ARCHIVE : FILE_ATTRIBUTE_DIRECTORY, - is_file ? size : 0, is_file ? mtime : (FILETIME){0, 0}); - if (!is_file) { - get_dir_bucket(child_key, 1); - strncpy(parent, child_key, sizeof(parent) - 1); - parent[sizeof(parent) - 1] = '\0'; - } - free(seg); - } -} - -static int entry_cmp(const void* a, const void* b) -{ - const VfsEntry* ea = (const VfsEntry*)a; - const VfsEntry* eb = (const VfsEntry*)b; - return strcmp(ea->key, eb->key); -} - -static VfsEntry* lookup_entry(const char* key) -{ - size_t lo = 0; - size_t hi = g_entry_count; - while (lo < hi) { - size_t mid = lo + (hi - lo) / 2; - int cmp = strcmp(key, g_entries[mid].key); - if (cmp == 0) return &g_entries[mid]; - if (cmp < 0) hi = mid; - else lo = mid + 1; - } - return NULL; -} - -static void append_log(const char* buf, DWORD len) -{ - HANDLE err = GetStdHandle(STD_ERROR_HANDLE); - if (err != NULL && err != INVALID_HANDLE_VALUE) { - DWORD wrote = 0; - WriteFile(err, buf, len, &wrote, NULL); - } - - HANDLE f = CreateFileA("Z:\\tmp\\fluorine_vfs_appinit.log", - FILE_APPEND_DATA, - FILE_SHARE_READ | FILE_SHARE_WRITE | - FILE_SHARE_DELETE, - NULL, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (f != INVALID_HANDLE_VALUE) { - DWORD wrote = 0; - WriteFile(f, buf, len, &wrote, NULL); - CloseHandle(f); - } -} - -static DWORD copy_str(char* dst, DWORD pos, DWORD cap, const char* src) -{ - while (*src && pos + 1 < cap) dst[pos++] = *src++; - return pos; -} - -static DWORD copy_u64(char* dst, DWORD pos, DWORD cap, unsigned long long v) -{ - char rev[32]; - int n = 0; - if (v == 0) { - rev[n++] = '0'; - } else { - while (v > 0 && n < (int)sizeof(rev)) { - rev[n++] = (char)('0' + (v % 10)); - v /= 10; - } - } - while (n > 0 && pos + 1 < cap) dst[pos++] = rev[--n]; - return pos; -} - -static void log_msg(const char* msg) -{ - char line[512]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] "); - pos = copy_str(line, pos, sizeof(line), msg); - pos = copy_str(line, pos, sizeof(line), "\n"); - append_log(line, pos); -} - -static void log_hook_status(const char* name, MH_STATUS st) -{ - char line[512]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] hook "); - pos = copy_str(line, pos, sizeof(line), name); - pos = copy_str(line, pos, sizeof(line), " status="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)(int)st); - pos = copy_str(line, pos, sizeof(line), "\n"); - append_log(line, pos); -} - -static void log_phase(const char* name) -{ - ULONGLONG now = GetTickCount64(); - ULONGLONG start = g_phase_start_tick; - char line[512]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] phase "); - pos = copy_str(line, pos, sizeof(line), name); - pos = copy_str(line, pos, sizeof(line), " tick_ms="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)now); - if (start != 0) { - pos = copy_str(line, pos, sizeof(line), " since_hook_worker_ms="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)(now - start)); - } - pos = copy_str(line, pos, sizeof(line), " tid="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)GetCurrentThreadId()); - pos = copy_str(line, pos, sizeof(line), "\n"); - append_log(line, pos); -} - -static void log_stats(void) -{ - char line[512]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] nt_stats create="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_create); - pos = copy_str(line, pos, sizeof(line), " open="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_open); - pos = copy_str(line, pos, sizeof(line), " close="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_close); - pos = copy_str(line, pos, sizeof(line), " qdf="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_qdf); - pos = copy_str(line, pos, sizeof(line), " qdfex="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_qdfex); - pos = copy_str(line, pos, sizeof(line), " qattr="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_qattr); - pos = copy_str(line, pos, sizeof(line), " qfullattr="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_nt_qfullattr); - pos = copy_str(line, pos, sizeof(line), " redir_file="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_redirect_file); - pos = copy_str(line, pos, sizeof(line), " redir_miss="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_redirect_miss); - pos = copy_str(line, pos, sizeof(line), " redir_skip="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_redirect_skip); - pos = copy_str(line, pos, sizeof(line), " redir_dir="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_redirect_dir); - pos = copy_str(line, pos, sizeof(line), " dir_served="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_dir_query_served); - pos = copy_str(line, pos, sizeof(line), " dir_empty="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_dir_query_empty); - pos = copy_str(line, pos, sizeof(line), " dir_bypass="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_dir_query_bypass); - pos = copy_str(line, pos, sizeof(line), " attr_hit="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_attr_hit); - pos = copy_str(line, pos, sizeof(line), " attr_miss="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_attr_miss); - pos = copy_str(line, pos, sizeof(line), " attr_neg="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_attr_neg); - pos = copy_str(line, pos, sizeof(line), " attr_pass="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_attr_passthru); - pos = copy_str(line, pos, sizeof(line), " cache_hit="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_attr_cache_hit); - pos = copy_str(line, pos, sizeof(line), " cache_ins="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_attr_cache_insert); - pos = copy_str(line, pos, sizeof(line), " cache_full="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_attr_cache_full); - pos = copy_str(line, pos, sizeof(line), " k32w="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_gfaexw); - pos = copy_str(line, pos, sizeof(line), " k32a="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_gfaexa); - pos = copy_str(line, pos, sizeof(line), " k32_hit="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_attr_hit); - pos = copy_str(line, pos, sizeof(line), " k32_cache="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_cache_hit); - pos = copy_str(line, pos, sizeof(line), " k32_pass="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_passthru); - pos = copy_str(line, pos, sizeof(line), " k32_neg="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_neg); - pos = copy_str(line, pos, sizeof(line), " k32_fast="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_fast_path); - pos = copy_str(line, pos, sizeof(line), " k32_full="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_k32_full_path); - pos = copy_str(line, pos, sizeof(line), " live_ins="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_live_insert); - pos = copy_str(line, pos, sizeof(line), " open_neg="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_open_neg); - pos = copy_str(line, pos, sizeof(line), " mm_att="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_attached); - pos = copy_str(line, pos, sizeof(line), " mm_det="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_detached); - pos = copy_str(line, pos, sizeof(line), " mm_rd="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_reads); - pos = copy_str(line, pos, sizeof(line), " mm_by="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_bytes); - pos = copy_str(line, pos, sizeof(line), " mm_pt="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_passthru); - pos = copy_str(line, pos, sizeof(line), " mm_skz="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_skip_size); - pos = copy_str(line, pos, sizeof(line), " mm_mf="); - pos = copy_u64(line, pos, sizeof(line), - (unsigned long long)g_mmap_map_failed); - pos = copy_str(line, pos, sizeof(line), "\n"); - append_log(line, pos); -} - -static DWORD WINAPI stats_worker(void* unused) -{ - (void)unused; - for (int i = 0; i < 120; ++i) { - Sleep(1000); - if (g_installed) log_stats(); - } - log_phase("stats_worker_exit"); - return 0; -} - -static void start_stats_worker(void) -{ - if (InterlockedExchange(&g_stats_started, 1) != 0) return; - HANDLE th = CreateThread(NULL, 0, stats_worker, NULL, 0, NULL); - if (th != NULL) CloseHandle(th); -} - -static void log_index_loaded(void) -{ - char line[256]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] index files="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_entry_count); - pos = copy_str(line, pos, sizeof(line), " dirs="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_dir_count); - pos = copy_str(line, pos, sizeof(line), " children="); - pos = copy_u64(line, pos, sizeof(line), (unsigned long long)g_child_count); - pos = copy_str(line, pos, sizeof(line), "\n"); - append_log(line, pos); -} - -static int read_file_all(const char* path, char** out, size_t* out_len) -{ - *out = NULL; - *out_len = 0; - HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (f == INVALID_HANDLE_VALUE) return 0; - LARGE_INTEGER sz; - if (!GetFileSizeEx(f, &sz) || sz.QuadPart <= 0 || - sz.QuadPart > 512LL * 1024LL * 1024LL) { - CloseHandle(f); - return 0; - } - - size_t len = (size_t)sz.QuadPart; - char* buf = (char*)malloc(len + 1); - if (buf == NULL) { - CloseHandle(f); - return 0; - } - - size_t off = 0; - while (off < len) { - DWORD want = (DWORD)((len - off) > (1024 * 1024) ? (1024 * 1024) : (len - off)); - DWORD got = 0; - if (!ReadFile(f, buf + off, want, &got, NULL) || got == 0) { - free(buf); - CloseHandle(f); - return 0; - } - off += got; - } - CloseHandle(f); - buf[len] = '\0'; - *out = buf; - *out_len = len; - return 1; -} - -static int append_entry(char* key, WCHAR* real_nt, ULONG attrs, - unsigned long long size, FILETIME mtime) -{ - VfsEntry* next = - (VfsEntry*)realloc(g_entries, (g_entry_count + 1) * sizeof(VfsEntry)); - if (next == NULL) return 0; - g_entries = next; - g_entries[g_entry_count].key = key; - g_entries[g_entry_count].real_nt = real_nt; - g_entries[g_entry_count].attrs = attrs; - g_entries[g_entry_count].size = size; - g_entries[g_entry_count].mtime = mtime; - ++g_entry_count; - return 1; -} - -static int load_index(void) -{ - char index_path[1024]; - DWORD n = GetEnvironmentVariableA("FLUORINE_VFS_INDEX", index_path, - (DWORD)sizeof(index_path)); - if (n == 0 || n >= sizeof(index_path)) { - log_msg("index load skipped: FLUORINE_VFS_INDEX unset"); - return 0; - } - - char mount[1024]; - n = GetEnvironmentVariableA("FLUORINE_VFS_MOUNT", mount, (DWORD)sizeof(mount)); - if (n == 0 || n >= sizeof(mount)) { - log_msg("index load skipped: FLUORINE_VFS_MOUNT unset"); - return 0; - } - g_mount = heap_strndup(mount, strlen(mount)); - if (g_mount == NULL) return 0; - normalize_utf8_path(g_mount); - build_mount_prefixes(); - - char* file = NULL; - size_t file_len = 0; - if (!read_file_all(index_path, &file, &file_len)) { - log_msg("index read failed"); - return 0; - } - - char* line = file; - char* end = file + file_len; - while (line < end) { - char* nl = (char*)memchr(line, '\n', (size_t)(end - line)); - if (nl != NULL) *nl = '\0'; - if (strstr(line, "\"record\":\"entry\"") != NULL) { - char* type = json_string_field(line, "type"); - if (type != NULL && ascii_eq_ci(type, "file")) { - char* vp = json_string_field(line, "virtual_path"); - char* rp = json_string_field(line, "real_path"); - /* Accept both absolute (mod / Overwrite) and backing-relative - * (vanilla data dir) entries. Backing entries get real_nt=NULL - * — they exist for query_index_attrs_key and the negcache - * decision, but Hook_NtCreateFile skips redirect for them and - * falls through to Real_, which serves them via the FUSE - * mount path. Without this, every base-game file open without - * a preceding GetFileAttributes would hit the negcache and - * return STATUS_OBJECT_NAME_NOT_FOUND. */ - if (vp != NULL && rp != NULL) { - unsigned long long size = json_u64_field(line, "size"); - FILETIME mtime = - filetime_from_unix_ns(json_u64_field(line, "mtime_ns")); - add_path_to_dirs(vp, size, mtime); - normalize_utf8_path(vp); - WCHAR* real_nt = (rp[0] == '/') ? unix_path_to_nt_wide(rp) : NULL; - int unix_alloc_failed = (rp[0] == '/' && real_nt == NULL); - if (unix_alloc_failed || - !append_entry(vp, real_nt, FILE_ATTRIBUTE_ARCHIVE, size, - mtime)) { - free(vp); - if (real_nt != NULL) free(real_nt); - } - vp = NULL; - } - if (vp != NULL) free(vp); - if (rp != NULL) free(rp); - } - if (type != NULL) free(type); - } - if (nl == NULL) break; - line = nl + 1; - } - free(file); - - if (g_entry_count == 0) { - log_msg("index load failed: no files"); - return 0; - } - qsort(g_entries, g_entry_count, sizeof(VfsEntry), entry_cmp); - sort_dir_children(); - InterlockedExchange(&g_index_ready, 1); - log_index_loaded(); - return 1; -} - -static int nt_path_to_key(PUNICODE_STRING_FL us, char* out, size_t cap) -{ - if (us == NULL || us->Buffer == NULL || cap == 0) return 0; - int chars = us->Length / sizeof(WCHAR); - if (chars <= 0) return 0; - int need = WideCharToMultiByte(CP_UTF8, 0, us->Buffer, chars, NULL, 0, - NULL, NULL); - if (need <= 0 || (size_t)need + 1 >= cap) return 0; - WideCharToMultiByte(CP_UTF8, 0, us->Buffer, chars, out, (int)cap, NULL, - NULL); - out[need] = '\0'; - - char* p = out; - if (strncmp(p, "\\??\\", 4) == 0 || strncmp(p, "\\\\?\\", 4) == 0) p += 4; - if ((p[0] == 'Z' || p[0] == 'z') && p[1] == ':') { - p += 2; - while (*p == '\\' || *p == '/') ++p; - memmove(out, "/", 1); - memmove(out + 1, p, strlen(p) + 1); - } else { - if (p != out) memmove(out, p, strlen(p) + 1); - } - for (char* q = out; *q; ++q) { - if (*q == '\\') *q = '/'; - else *q = ascii_lower(*q); - } - while (strlen(out) > 1 && out[strlen(out) - 1] == '/') { - out[strlen(out) - 1] = '\0'; - } - - const char* cmp = out; - if (g_mount != NULL && g_mount[0] != '/' && cmp[0] == '/') ++cmp; - - size_t ml = g_mount ? strlen(g_mount) : 0; - if (ml == 0 || strncmp(cmp, g_mount, ml) != 0) return 0; - if (cmp[ml] == '\0') { - out[0] = '\0'; - return 1; - } - if (cmp[ml] != '/') return 0; - memmove(out, cmp + ml + 1, strlen(cmp + ml + 1) + 1); - return out[0] != '\0'; -} - -static int dos_wide_path_to_key(LPCWSTR path, char* out, size_t cap) -{ - if (path == NULL || out == NULL || cap == 0 || !g_index_ready) return 0; - out[0] = '\0'; - - int raw_bytes = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, - NULL, NULL); - if (raw_bytes > 0 && (size_t)raw_bytes < cap) { - WideCharToMultiByte(CP_UTF8, 0, path, -1, out, (int)cap, NULL, NULL); - - char* p = out; - if (strncmp(p, "\\\\?\\", 4) == 0 || strncmp(p, "\\??\\", 4) == 0) p += 4; - if ((p[0] == 'Z' || p[0] == 'z') && p[1] == ':') { - p += 2; - while (*p == '\\' || *p == '/') ++p; - memmove(out, "/", 1); - memmove(out + 1, p, strlen(p) + 1); - } else if (p != out) { - memmove(out, p, strlen(p) + 1); - } - - for (char* q = out; *q; ++q) { - if (*q == '\\') *q = '/'; - else *q = ascii_lower(*q); - } - while (strlen(out) > 1 && out[strlen(out) - 1] == '/') { - out[strlen(out) - 1] = '\0'; - } - - const char* cmp = out; - if (g_mount != NULL && g_mount[0] != '/' && cmp[0] == '/') ++cmp; - size_t ml = g_mount ? strlen(g_mount) : 0; - if (ml != 0 && strncmp(cmp, g_mount, ml) == 0) { - if (cmp[ml] == '\0') { - out[0] = '\0'; - return 1; - } - if (cmp[ml] == '/') { - memmove(out, cmp + ml + 1, strlen(cmp + ml + 1) + 1); - if (out[0] != '\0') { - InterlockedIncrement64(&g_k32_fast_path); - return 1; - } - } - } - - if (strncmp(out, "data/", 5) == 0 && out[5] != '\0') { - memmove(out, out + 5, strlen(out + 5) + 1); - InterlockedIncrement64(&g_k32_fast_path); - return 1; - } - } - - DWORD need = GetFullPathNameW(path, 0, NULL, NULL); - if (need == 0 || need > 32760) return 0; - - WCHAR* full = (WCHAR*)malloc(((size_t)need + 1) * sizeof(WCHAR)); - if (full == NULL) return 0; - DWORD got = GetFullPathNameW(path, need + 1, full, NULL); - if (got == 0 || got >= need + 1) { - free(full); - return 0; - } - - int bytes = WideCharToMultiByte(CP_UTF8, 0, full, -1, NULL, 0, NULL, NULL); - if (bytes <= 0 || (size_t)bytes >= cap) { - free(full); - return 0; - } - WideCharToMultiByte(CP_UTF8, 0, full, -1, out, (int)cap, NULL, NULL); - free(full); - - char* p = out; - if (strncmp(p, "\\\\?\\", 4) == 0) p += 4; - if ((p[0] == 'Z' || p[0] == 'z') && p[1] == ':') { - p += 2; - while (*p == '\\' || *p == '/') ++p; - memmove(out, "/", 1); - memmove(out + 1, p, strlen(p) + 1); - } else { - if (p != out) memmove(out, p, strlen(p) + 1); - } - for (char* q = out; *q; ++q) { - if (*q == '\\') *q = '/'; - else *q = ascii_lower(*q); - } - while (strlen(out) > 1 && out[strlen(out) - 1] == '/') { - out[strlen(out) - 1] = '\0'; - } - - const char* cmp = out; - if (g_mount != NULL && g_mount[0] != '/' && cmp[0] == '/') ++cmp; - size_t ml = g_mount ? strlen(g_mount) : 0; - if (ml == 0 || strncmp(cmp, g_mount, ml) != 0) return 0; - if (cmp[ml] == '\0') { - out[0] = '\0'; - return 1; - } - if (cmp[ml] != '/') return 0; - memmove(out, cmp + ml + 1, strlen(cmp + ml + 1) + 1); - InterlockedIncrement64(&g_k32_full_path); - return out[0] != '\0'; -} - -/* Module-handle-sensitive: Windows registers loaded PE modules under - * the string used to open them. Redirecting those breaks - * GetModuleHandleW lookups, script-extender trampoline systems, and - * compiled-script (.pex) version probes. Archives (BSA/BA2/ESP/ESM/ - * ESL) carry no path-string semantics on Linux because reads stream - * from the resulting file handle, so they redirect freely. */ -static int protected_ext_module(const char* key) -{ - const char* dot = strrchr(key, '.'); - if (dot == NULL || dot[1] == '\0') return 0; - ++dot; - return strcmp(dot, "dll") == 0 || strcmp(dot, "exe") == 0 || - strcmp(dot, "pex") == 0; -} - -static int build_redirect_oa(POBJECT_ATTRIBUTES_FL src, - UNICODE_STRING_FL* out_us, - OBJECT_ATTRIBUTES_FL* out_oa, - const WCHAR* real_nt) -{ - size_t len = lstrlenW(real_nt); - if (len == 0 || len >= (sizeof(t_redirect_path) / sizeof(t_redirect_path[0]))) - return 0; - memcpy(t_redirect_path, real_nt, (len + 1) * sizeof(WCHAR)); - out_us->Buffer = t_redirect_path; - out_us->Length = (USHORT)(len * sizeof(WCHAR)); - out_us->MaximumLength = out_us->Length; - *out_oa = *src; - out_oa->RootDirectory = NULL; - out_oa->ObjectName = out_us; - return 1; -} - -static int ensure_dummy_dir(void) -{ - if (g_dummy_dir != INVALID_HANDLE_VALUE) return 1; - g_dummy_dir = CreateFileW(L"Z:\\tmp", GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | - FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (g_dummy_dir == INVALID_HANDLE_VALUE) { - log_msg("dummy dir open failed"); - return 0; - } - return 1; -} - -static int duplicate_dummy_dir(PHANDLE out) -{ - if (out == NULL || !ensure_dummy_dir()) return 0; - HANDLE dup = INVALID_HANDLE_VALUE; - if (!DuplicateHandle(GetCurrentProcess(), g_dummy_dir, GetCurrentProcess(), - &dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { - return 0; - } - *out = dup; - return 1; -} - -static int is_write_or_create(ACCESS_MASK access, ULONG disp, int is_create) -{ - if (is_create && disp != 1u) return 1; - return (access & (0x40000000u | 0x00010000u | 0x00000002u | - 0x00000004u | 0x00000100u)) != 0; -} - -static int register_dir_handle(HANDLE h, DirBucket* bucket) -{ - if (h == NULL || h == INVALID_HANDLE_VALUE || bucket == NULL || - !g_dir_lock_ready) { - return 0; - } - EnterCriticalSection(&g_dir_lock); - if (g_dir_handle_count == g_dir_handle_cap) { - size_t next_cap = g_dir_handle_cap ? g_dir_handle_cap * 2 : 64; - DirHandleState* next = - (DirHandleState*)realloc(g_dir_handles, - next_cap * sizeof(DirHandleState)); - if (next == NULL) { - LeaveCriticalSection(&g_dir_lock); - return 0; - } - g_dir_handles = next; - g_dir_handle_cap = next_cap; - } - DirHandleState* st = &g_dir_handles[g_dir_handle_count++]; - memset(st, 0, sizeof(*st)); - st->handle = h; - st->bucket = bucket; - LeaveCriticalSection(&g_dir_lock); - return 1; -} - -static void unregister_dir_handle(HANDLE h) -{ - if (!g_dir_lock_ready || h == NULL || h == INVALID_HANDLE_VALUE) return; - EnterCriticalSection(&g_dir_lock); - for (size_t i = 0; i < g_dir_handle_count; ++i) { - if (g_dir_handles[i].handle == h) { - if (g_dir_handles[i].filter != NULL) free(g_dir_handles[i].filter); - g_dir_handles[i] = g_dir_handles[g_dir_handle_count - 1]; - --g_dir_handle_count; - break; - } - } - LeaveCriticalSection(&g_dir_lock); -} - -static DirHandleState* find_dir_handle_locked(HANDLE h) -{ - if (h == NULL || h == INVALID_HANDLE_VALUE) return NULL; - for (size_t i = 0; i < g_dir_handle_count; ++i) { - if (g_dir_handles[i].handle == h) return &g_dir_handles[i]; - } - return NULL; -} - -static WCHAR wlower(WCHAR c) -{ - return (c >= L'A' && c <= L'Z') ? (WCHAR)(c + 32) : c; -} - -static int wildcard_match_w(const WCHAR* name, size_t nlen, - const WCHAR* pat, size_t plen) -{ - size_t i = 0, j = 0; - size_t star_i = (size_t)-1, star_j = 0; - while (i < nlen) { - if (j < plen) { - WCHAR p = pat[j]; - if (p == L'<') p = L'*'; - else if (p == L'>') p = L'?'; - else if (p == L'"') p = L'.'; - if (p == L'*') { - star_j = j + 1; - star_i = i; - ++j; - continue; - } - if (p == L'?' || wlower(name[i]) == p) { - ++i; - ++j; - continue; - } - } - if (star_i != (size_t)-1) { - j = star_j; - ++star_i; - i = star_i; - continue; - } - return 0; - } - while (j < plen) { - WCHAR p = pat[j++]; - if (p == L'<') p = L'*'; - if (p != L'*') return 0; - } - return 1; -} - -static int child_matches_filter(DirHandleState* st, const ChildEntry* e) -{ - if (st->filter == NULL || st->filter_len == 0 || - (st->filter_len == 1 && st->filter[0] == L'*')) { - return 1; - } - size_t n = lstrlenW(e->name); - return wildcard_match_w(e->name, n, st->filter, st->filter_len); -} - -static size_t dir_info_header_size(ULONG info_class) -{ - switch (info_class) { - case FL_FileDirectoryInformation: - return offsetof(FL_FILE_DIRECTORY_INFORMATION, FileName); - case FL_FileFullDirectoryInformation: - return offsetof(FL_FILE_FULL_DIR_INFORMATION, FileName); - case FL_FileBothDirectoryInformation: - return offsetof(FL_FILE_BOTH_DIR_INFORMATION, FileName); - case FL_FileIdBothDirectoryInformation: - return offsetof(FL_FILE_ID_BOTH_DIR_INFORMATION, FileName); - case FL_FileIdFullDirectoryInformation: - return offsetof(FL_FILE_ID_FULL_DIR_INFORMATION, FileName); - case FL_FileNamesInformation: - return offsetof(FL_FILE_NAMES_INFORMATION, FileName); - default: - return 0; - } -} - -static size_t write_dir_entry(unsigned char* buf, size_t cap, size_t off, - ULONG info_class, const ChildEntry* e) -{ - size_t hdr = dir_info_header_size(info_class); - if (hdr == 0) return 0; - size_t name_chars = lstrlenW(e->name); - size_t name_bytes = name_chars * sizeof(WCHAR); - size_t total_raw = hdr + name_bytes; - size_t total = (total_raw + 7u) & ~(size_t)7u; - if (off + total_raw > cap) return 0; - - unsigned char* p = buf + off; - memset(p, 0, total); - if (info_class != FL_FileNamesInformation) { - FL_FILE_DIRECTORY_INFORMATION* base = - (FL_FILE_DIRECTORY_INFORMATION*)p; - base->CreationTime.LowPart = e->mtime.dwLowDateTime; - base->CreationTime.HighPart = (LONG)e->mtime.dwHighDateTime; - base->LastAccessTime = base->CreationTime; - base->LastWriteTime = base->CreationTime; - base->ChangeTime = base->CreationTime; - base->EndOfFile.QuadPart = (LONGLONG)e->size; - base->AllocationSize.QuadPart = - (LONGLONG)((e->size + 4095ULL) & ~4095ULL); - base->FileAttributes = e->attrs; - base->FileNameLength = (ULONG)name_bytes; - } else { - FL_FILE_NAMES_INFORMATION* base = (FL_FILE_NAMES_INFORMATION*)p; - base->FileNameLength = (ULONG)name_bytes; - } - memcpy(p + hdr, e->name, name_bytes); - return total; -} - -static void patch_next(unsigned char* buf, size_t prev, ULONG delta) -{ - memcpy(buf + prev, &delta, sizeof(delta)); -} - -static void fill_basic_info(FL_FILE_BASIC_INFORMATION* out, ULONG attrs, - FILETIME mtime) -{ - memset(out, 0, sizeof(*out)); - out->CreationTime.LowPart = mtime.dwLowDateTime; - out->CreationTime.HighPart = (LONG)mtime.dwHighDateTime; - out->LastAccessTime = out->CreationTime; - out->LastWriteTime = out->CreationTime; - out->ChangeTime = out->CreationTime; - out->FileAttributes = attrs; -} - -static void fill_network_info(FL_FILE_NETWORK_OPEN_INFORMATION* out, - ULONG attrs, unsigned long long size, - FILETIME mtime) -{ - memset(out, 0, sizeof(*out)); - out->CreationTime.LowPart = mtime.dwLowDateTime; - out->CreationTime.HighPart = (LONG)mtime.dwHighDateTime; - out->LastAccessTime = out->CreationTime; - out->LastWriteTime = out->CreationTime; - out->ChangeTime = out->CreationTime; - out->EndOfFile.QuadPart = (LONGLONG)size; - out->AllocationSize.QuadPart = (LONGLONG)((size + 4095ULL) & ~4095ULL); - out->FileAttributes = attrs; -} - -static void fill_win32_attr_data(WIN32_FILE_ATTRIBUTE_DATA* out, ULONG attrs, - unsigned long long size, FILETIME mtime) -{ - memset(out, 0, sizeof(*out)); - out->dwFileAttributes = attrs; - out->ftCreationTime = mtime; - out->ftLastAccessTime = mtime; - out->ftLastWriteTime = mtime; - out->nFileSizeHigh = (DWORD)(size >> 32); - out->nFileSizeLow = (DWORD)(size & 0xffffffffULL); -} - -static int attr_path_key(POBJECT_ATTRIBUTES_FL oa, char* key, size_t key_cap) -{ - if (oa == NULL || oa->ObjectName == NULL || oa->RootDirectory != NULL || - !g_index_ready) { - return 0; - } - return nt_path_to_key(oa->ObjectName, key, key_cap); -} - -static int query_index_attrs_key(const char* key, ULONG* attrs, - unsigned long long* size, FILETIME* mtime) -{ - VfsEntry* e = lookup_entry(key); - if (e != NULL) { - *attrs = e->attrs; - *size = e->size; - *mtime = e->mtime; - return 1; - } - - DirBucket* b = get_dir_bucket(key, 0); - if (b != NULL) { - *attrs = FILE_ATTRIBUTE_DIRECTORY; - *size = 0; - mtime->dwLowDateTime = 0; - mtime->dwHighDateTime = 0; - return 1; - } - return 0; -} - -static int should_neg_attr_miss(const char* key) -{ - static LONG cached = -1; - LONG v = cached; - if (v < 0) { - char buf[16]; - DWORD n = GetEnvironmentVariableA("FLUORINE_VFS_NEG_ATTR_MISS", buf, - (DWORD)sizeof(buf)); - v = (n > 0 && n < sizeof(buf) && buf[0] == '1') ? 1 : 0; - InterlockedCompareExchange(&cached, v, -1); - v = cached; - } - if (!v) return 0; - if (key == NULL || key[0] == '\0') return 0; - return !protected_ext_module(key); -} - -/* ----- Path cache (PathCache equivalent) ----- - * - * For paths under the VFS mount that miss our static index, the first - * Real_NtQuery* roundtrip to Wine -> Z: -> FUSE answers definitively - * (FUSE is authoritative for the live tree, including Overwrite - * writes). We memoize the answer so subsequent probes for the same - * path skip the Real_ syscall entirely. Game-agnostic — no subtree - * carve-outs needed because we always real-probe before answering. - * - * Negatives are cached too. Mid-boot writes that would flip a - * negative entry to positive are caught by attr_cache_mark_exists(), - * called from Hook_NtCreateFile after a successful write/create — - * attr_cache_insert() overwrites any prior entry for the same key. */ - -typedef struct AttrCacheEntry -{ - char* key; - ULONG attrs; - unsigned long long size; - FILETIME mtime; - unsigned char exists; - struct AttrCacheEntry* next; -} AttrCacheEntry; - -#define FL_ATTR_CACHE_BUCKETS 8192u -#define FL_ATTR_CACHE_MAX 32768u -static AttrCacheEntry** g_attr_cache = NULL; -static volatile LONG64 g_attr_cache_count = 0; -static CRITICAL_SECTION g_attr_cache_lock; -static volatile LONG g_attr_cache_ready = 0; - -static unsigned long key_hash_djb2(const char* s) -{ - unsigned long h = 5381u; - while (*s) h = (h * 33u) ^ (unsigned char)*s++; - return h; -} - -static void attr_cache_init(void) -{ - if (InterlockedExchange(&g_attr_cache_ready, 1) != 0) return; - g_attr_cache = (AttrCacheEntry**)calloc(FL_ATTR_CACHE_BUCKETS, - sizeof(AttrCacheEntry*)); - InitializeCriticalSection(&g_attr_cache_lock); -} - -static int attr_cache_lookup(const char* key, ULONG* attrs, - unsigned long long* size, FILETIME* mtime, - int* exists) -{ - if (!g_attr_cache_ready || g_attr_cache == NULL || key == NULL || - key[0] == '\0') - return 0; - unsigned long h = key_hash_djb2(key) % FL_ATTR_CACHE_BUCKETS; - EnterCriticalSection(&g_attr_cache_lock); - for (AttrCacheEntry* e = g_attr_cache[h]; e != NULL; e = e->next) { - if (strcmp(e->key, key) == 0) { - *attrs = e->attrs; - *size = e->size; - *mtime = e->mtime; - *exists = e->exists ? 1 : 0; - LeaveCriticalSection(&g_attr_cache_lock); - return 1; - } - } - LeaveCriticalSection(&g_attr_cache_lock); - return 0; -} - -static void attr_cache_insert(const char* key, ULONG attrs, - unsigned long long size, FILETIME mtime, - int exists) -{ - if (!g_attr_cache_ready || g_attr_cache == NULL || key == NULL || - key[0] == '\0') - return; - if (g_attr_cache_count >= (LONG64)FL_ATTR_CACHE_MAX) { - InterlockedIncrement64(&g_attr_cache_full); - return; - } - size_t klen = strlen(key); - unsigned long h = key_hash_djb2(key) % FL_ATTR_CACHE_BUCKETS; - EnterCriticalSection(&g_attr_cache_lock); - for (AttrCacheEntry* e = g_attr_cache[h]; e != NULL; e = e->next) { - if (strcmp(e->key, key) == 0) { - e->attrs = attrs; - e->size = size; - e->mtime = mtime; - e->exists = (unsigned char)(exists ? 1 : 0); - LeaveCriticalSection(&g_attr_cache_lock); - return; - } - } - AttrCacheEntry* ne = - (AttrCacheEntry*)malloc(sizeof(AttrCacheEntry)); - if (ne == NULL) { - LeaveCriticalSection(&g_attr_cache_lock); - return; - } - ne->key = heap_strndup(key, klen); - if (ne->key == NULL) { - free(ne); - LeaveCriticalSection(&g_attr_cache_lock); - return; - } - ne->attrs = attrs; - ne->size = size; - ne->mtime = mtime; - ne->exists = (unsigned char)(exists ? 1 : 0); - ne->next = g_attr_cache[h]; - g_attr_cache[h] = ne; - InterlockedIncrement64(&g_attr_cache_count); - InterlockedIncrement64(&g_attr_cache_insert); - LeaveCriticalSection(&g_attr_cache_lock); -} - -/* Promote a path to "exists" after a successful create/write. - * Uses caller-supplied attrs/size/mtime if available, otherwise - * stamps a defensive default of FILE_ATTRIBUTE_NORMAL with zero - * timestamp so subsequent attr probes return STATUS_SUCCESS instead - * of stale ENOENT. */ -static void attr_cache_mark_exists(const char* key) -{ - if (!g_attr_cache_ready || key == NULL || key[0] == '\0') return; - FILETIME ft = {0, 0}; - attr_cache_insert(key, 0x00000080u /* FILE_ATTRIBUTE_NORMAL */, 0, ft, - 1); - InterlockedIncrement64(&g_live_insert); -} - -/* ----- mmap shim ----- - * - * Engine ReadFile loops on plugin/archive loads cost ~20-50us per - * call in pure Wine syscall overhead, dwarfing the ~50ns memcpy that - * actually moves the bytes. For redirected handles (which point at a - * real backing file under the mount) we opportunistically map the - * whole file into our process address space and serve subsequent - * NtReadFile calls from memcpy. NtSetInformationFile/ - * NtQueryInformationFile keep the tracked cursor in sync with what - * the engine sets explicitly. - * - * Skipped: files >32 MB (RAM pressure for full map; BSAs handled in - * a later phase via header-window mapping), zero-byte files, - * overlapped reads, write-intent opens (write paths never reach - * mmap_attach because they bail before redirect). */ - -typedef struct MmapEntry -{ - HANDLE h_file; - HANDLE h_section; - void* base; - LONGLONG size; - volatile LONGLONG pos; - struct MmapEntry* next; -} MmapEntry; - -#define FL_MMAP_BUCKETS 4096u -/* Bumped to cover DXVK pipeline cache (.bin/.lut) and similar big - * read-only files. Linux kernel handles physical page eviction; we - * only consume virtual address space, of which 64-bit has plenty. */ -#define FL_MMAP_MAX_SIZE (1024LL * 1024LL * 1024LL) - -static MmapEntry** g_mmap_table = NULL; -static SRWLOCK g_mmap_lock = SRWLOCK_INIT; -static volatile LONG g_mmap_ready = 0; - -static unsigned long handle_hash(HANDLE h) -{ - uintptr_t v = (uintptr_t)h; - v ^= v >> 33; - v *= 0xff51afd7ed558ccdULL; - v ^= v >> 33; - return (unsigned long)v; -} - -static void mmap_init(void) -{ - if (InterlockedExchange(&g_mmap_ready, 1) != 0) return; - g_mmap_table = - (MmapEntry**)calloc(FL_MMAP_BUCKETS, sizeof(MmapEntry*)); - /* SRWLOCK already statically initialized via SRWLOCK_INIT. */ -} - -static MmapEntry* mmap_lookup(HANDLE h) -{ - if (!g_mmap_ready || g_mmap_table == NULL || h == NULL || - h == INVALID_HANDLE_VALUE) - return NULL; - unsigned long b = handle_hash(h) % FL_MMAP_BUCKETS; - AcquireSRWLockShared(&g_mmap_lock); - for (MmapEntry* e = g_mmap_table[b]; e != NULL; e = e->next) { - if (e->h_file == h) { - ReleaseSRWLockShared(&g_mmap_lock); - return e; - } - } - ReleaseSRWLockShared(&g_mmap_lock); - return NULL; -} - -static int mmap_disabled(void) -{ - static volatile LONG cached = -1; - LONG v = cached; - if (v < 0) { - char buf[16]; - DWORD n = GetEnvironmentVariableA("FLUORINE_VFS_NO_MMAP", buf, - (DWORD)sizeof(buf)); - v = (n > 0 && n < sizeof(buf) && buf[0] == '1') ? 1 : 0; - InterlockedCompareExchange(&cached, v, -1); - v = cached; - } - return v; -} - -static void mmap_attach(HANDLE h, const char* key) -{ - if (mmap_disabled()) return; - if (!g_mmap_ready || h == NULL || h == INVALID_HANDLE_VALUE) return; - /* Avoid double-attach in unlikely re-open of same kernel handle. */ - if (mmap_lookup(h) != NULL) return; - - IO_STATUS_BLOCK_FL iosb; - FL_FILE_STANDARD_INFORMATION fsi; - memset(&fsi, 0, sizeof(fsi)); - if (Real_NtQueryInformationFile == NULL) return; - NTSTATUS qs = - Real_NtQueryInformationFile(h, &iosb, &fsi, (ULONG)sizeof(fsi), - FL_FileStandardInformation); - if (qs != STATUS_SUCCESS) return; - if (fsi.Directory) return; - LONGLONG size = fsi.EndOfFile.QuadPart; - if (size <= 0) { - InterlockedIncrement64(&g_mmap_skip_zero); - return; - } - if (size > FL_MMAP_MAX_SIZE) { - InterlockedIncrement64(&g_mmap_skip_size); - return; - } - - HANDLE sec = CreateFileMappingW(h, NULL, PAGE_READONLY, 0, 0, NULL); - if (sec == NULL) { - InterlockedIncrement64(&g_mmap_map_failed); - return; - } - void* base = MapViewOfFile(sec, FILE_MAP_READ, 0, 0, 0); - if (base == NULL) { - CloseHandle(sec); - InterlockedIncrement64(&g_mmap_view_failed); - return; - } - - MmapEntry* ne = (MmapEntry*)malloc(sizeof(MmapEntry)); - if (ne == NULL) { - UnmapViewOfFile(base); - CloseHandle(sec); - return; - } - ne->h_file = h; - ne->h_section = sec; - ne->base = base; - ne->size = size; - ne->pos = 0; - unsigned long b = handle_hash(h) % FL_MMAP_BUCKETS; - AcquireSRWLockExclusive(&g_mmap_lock); - ne->next = g_mmap_table[b]; - g_mmap_table[b] = ne; - ReleaseSRWLockExclusive(&g_mmap_lock); - InterlockedIncrement64(&g_mmap_attached); - if (key != NULL && - InterlockedCompareExchange(&g_first_mmap_logged, 1, 0) == 0) { - log_phase("first_indexed_mmap_attach"); - } -} - -static void mmap_detach(HANDLE h) -{ - if (!g_mmap_ready || g_mmap_table == NULL || h == NULL || - h == INVALID_HANDLE_VALUE) - return; - unsigned long b = handle_hash(h) % FL_MMAP_BUCKETS; - AcquireSRWLockExclusive(&g_mmap_lock); - MmapEntry** pp = &g_mmap_table[b]; - while (*pp != NULL) { - if ((*pp)->h_file == h) { - MmapEntry* victim = *pp; - *pp = victim->next; - ReleaseSRWLockExclusive(&g_mmap_lock); - UnmapViewOfFile(victim->base); - CloseHandle(victim->h_section); - free(victim); - InterlockedIncrement64(&g_mmap_detached); - return; - } - pp = &(*pp)->next; - } - ReleaseSRWLockExclusive(&g_mmap_lock); -} - -/* NtReadFile ByteOffset sentinels (per MSDN/ReactOS): - * If ByteOffset is NULL or its value == FILE_USE_FILE_POINTER_POSITION - * (-2 == 0xFFFFFFFFFFFFFFFE), reads from current file pointer. */ -static int byte_offset_use_pointer(PLARGE_INTEGER bo) -{ - if (bo == NULL) return 1; - if (bo->QuadPart == (LONGLONG)0xFFFFFFFFFFFFFFFEULL) return 1; - if (bo->QuadPart < 0) return 1; - return 0; -} - -static NTSTATUS NTAPI Hook_NtReadFile(HANDLE h, HANDLE event, - PIO_APC_ROUTINE_FL apc, PVOID apc_ctx, - PIO_STATUS_BLOCK_FL iosb, - PVOID buf, ULONG len, - PLARGE_INTEGER bo, PULONG key_arg) -{ - /* Pass overlapped reads through; engine plugin loads are sync. */ - if (event != NULL || apc != NULL || apc_ctx != NULL || buf == NULL) { - return Real_NtReadFile(h, event, apc, apc_ctx, iosb, buf, len, bo, - key_arg); - } - MmapEntry* e = mmap_lookup(h); - if (e == NULL) { - return Real_NtReadFile(h, event, apc, apc_ctx, iosb, buf, len, bo, - key_arg); - } - LONGLONG start; - int advance_pos; - if (byte_offset_use_pointer(bo)) { - start = e->pos; - advance_pos = 1; - } else { - start = bo->QuadPart; - advance_pos = 0; - } - if (start >= e->size) { - /* File may have grown through this handle since we mapped it. - * Sync Wine's kernel-side file pointer to our tracked position so - * the fall-through Real_NtReadFile (which uses bo or the kernel - * pointer) reads from the correct offset. Without this sync, our - * mmap-served reads advance e->pos but leave the kernel's pointer - * stale at the position of the last NtSetInformationFile call, - * and the fall-through reads garbage. */ - if (advance_pos && Real_NtSetInformationFile != NULL) { - FL_FILE_POSITION_INFORMATION pi; - pi.CurrentByteOffset.QuadPart = e->pos; - IO_STATUS_BLOCK_FL temp_iosb; - Real_NtSetInformationFile(h, &temp_iosb, &pi, - (ULONG)sizeof(pi), - FL_FilePositionInformation); - } - InterlockedIncrement64(&g_mmap_passthru); - return Real_NtReadFile(h, event, apc, apc_ctx, iosb, buf, len, bo, - key_arg); - } - LONGLONG remain = e->size - start; - ULONG to_copy = (remain < (LONGLONG)len) ? (ULONG)remain : len; - memcpy(buf, (const char*)e->base + start, to_copy); - if (advance_pos) { - e->pos = start + (LONGLONG)to_copy; - } - if (iosb != NULL) { - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = to_copy; - } - InterlockedIncrement64(&g_mmap_reads); - InterlockedAdd64(&g_mmap_bytes, (LONG64)to_copy); - return STATUS_SUCCESS; -} - -static NTSTATUS NTAPI Hook_NtSetInformationFile(HANDLE h, - PIO_STATUS_BLOCK_FL iosb, - PVOID info, ULONG len, - ULONG info_class) -{ - NTSTATUS rs = Real_NtSetInformationFile(h, iosb, info, len, info_class); - if (rs == STATUS_SUCCESS && info_class == FL_FilePositionInformation && - info != NULL && len >= sizeof(FL_FILE_POSITION_INFORMATION)) { - MmapEntry* e = mmap_lookup(h); - if (e != NULL) { - FL_FILE_POSITION_INFORMATION* pi = - (FL_FILE_POSITION_INFORMATION*)info; - e->pos = pi->CurrentByteOffset.QuadPart; - } - } - return rs; -} - -static NTSTATUS NTAPI Hook_NtQueryInformationFile(HANDLE h, - PIO_STATUS_BLOCK_FL iosb, - PVOID info, ULONG len, - ULONG info_class) -{ - /* Most callers use this for size/position. Serve from our state to - * avoid a redundant kernel transition when we have it. */ - MmapEntry* e = mmap_lookup(h); - if (e != NULL && info != NULL) { - if (info_class == FL_FileStandardInformation && - len >= sizeof(FL_FILE_STANDARD_INFORMATION)) { - FL_FILE_STANDARD_INFORMATION* fsi = - (FL_FILE_STANDARD_INFORMATION*)info; - memset(fsi, 0, sizeof(*fsi)); - fsi->EndOfFile.QuadPart = e->size; - fsi->AllocationSize.QuadPart = - (e->size + 4095LL) & ~4095LL; - fsi->NumberOfLinks = 1; - if (iosb != NULL) { - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = sizeof(*fsi); - } - return STATUS_SUCCESS; - } - if (info_class == FL_FilePositionInformation && - len >= sizeof(FL_FILE_POSITION_INFORMATION)) { - FL_FILE_POSITION_INFORMATION* pi = - (FL_FILE_POSITION_INFORMATION*)info; - pi->CurrentByteOffset.QuadPart = e->pos; - if (iosb != NULL) { - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = sizeof(*pi); - } - return STATUS_SUCCESS; - } - } - return Real_NtQueryInformationFile(h, iosb, info, len, info_class); -} - -static NTSTATUS serve_dir_query_locked(DirHandleState* st, PVOID info, - ULONG len, ULONG info_class, - int restart, int single, - PUNICODE_STRING_FL file_name, - PIO_STATUS_BLOCK_FL iosb) -{ - if (iosb == NULL) return STATUS_INFO_LENGTH_MISMATCH; - if (restart) { - st->cursor = 0; - st->filter_set = 0; - if (st->filter != NULL) { - free(st->filter); - st->filter = NULL; - } - st->filter_len = 0; - } - if (!st->filter_set) { - if (file_name != NULL && file_name->Buffer != NULL && - file_name->Length > 0) { - st->filter_len = file_name->Length / sizeof(WCHAR); - st->filter = - (WCHAR*)malloc((st->filter_len + 1) * sizeof(WCHAR)); - if (st->filter != NULL) { - for (size_t i = 0; i < st->filter_len; ++i) { - st->filter[i] = wlower(file_name->Buffer[i]); - } - st->filter[st->filter_len] = 0; - } else { - st->filter_len = 0; - } - } - st->filter_set = 1; - } - - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = 0; - if (info == NULL || len < 16) { - iosb->u.Status = STATUS_INFO_LENGTH_MISMATCH; - return STATUS_INFO_LENGTH_MISMATCH; - } - - unsigned char* out = (unsigned char*)info; - size_t off = 0; - size_t prev = (size_t)-1; - size_t emitted = 0; - while (st->cursor < st->bucket->child_count) { - ChildEntry* e = &st->bucket->children[st->cursor]; - if (!child_matches_filter(st, e)) { - ++st->cursor; - continue; - } - size_t wrote = write_dir_entry(out, len, off, info_class, e); - if (wrote == 0) { - if (emitted > 0) break; - iosb->u.Status = STATUS_BUFFER_OVERFLOW; - return STATUS_BUFFER_OVERFLOW; - } - if (prev != (size_t)-1) patch_next(out, prev, (ULONG)(off - prev)); - prev = off; - off += wrote; - ++emitted; - ++st->cursor; - if (single) break; - } - - if (emitted == 0) { - iosb->u.Status = STATUS_NO_MORE_FILES; - iosb->Information = 0; - InterlockedIncrement64(&g_dir_query_empty); - return STATUS_NO_MORE_FILES; - } - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = off; - InterlockedIncrement64(&g_dir_query_served); - return STATUS_SUCCESS; -} - -static int should_skip_redirect(ACCESS_MASK access, ULONG disp, ULONG opts, - int is_create) -{ - if (t_inside_redirect > 0) return 1; - if (!g_index_ready) return 1; - if (is_create && disp != 1u) return 1; - if (opts & 0x00000001u) return 1; /* FILE_DIRECTORY_FILE */ - if (access & (0x40000000u | 0x10000000u | 0x00010000u | - 0x00000002u | 0x00000004u | 0x00000010u | - 0x00000100u)) { - return 1; - } - return 0; -} - -static NTSTATUS NTAPI Hook_NtCreateFile(PHANDLE out, ACCESS_MASK access, - POBJECT_ATTRIBUTES_FL oa, - PIO_STATUS_BLOCK_FL iosb, - PLARGE_INTEGER alloc_sz, - ULONG file_attrs, ULONG share, - ULONG disp, ULONG opts, PVOID ea, - ULONG ea_len) -{ - InterlockedIncrement64(&g_nt_create); - /* Mount-scope gate: anything outside the FUSE mount is invisible to the - * bridge. Pass through immediately, no normalization, no cache. */ - if (g_index_ready && t_inside_redirect == 0 && oa != NULL && - oa->ObjectName != NULL && oa->RootDirectory == NULL && - !us_under_mount(oa->ObjectName)) { - return Real_NtCreateFile(out, access, oa, iosb, alloc_sz, file_attrs, - share, disp, opts, ea, ea_len); - } - char key[2048]; - key[0] = '\0'; - int have_key = 0; - int write_intent = 0; - if (oa != NULL && oa->ObjectName != NULL && oa->RootDirectory == NULL && - t_inside_redirect == 0 && g_index_ready) { - if (nt_path_to_key(oa->ObjectName, key, sizeof(key))) { - have_key = 1; - if (InterlockedCompareExchange(&g_first_create_logged, 1, 0) == 0) { - log_phase("first_vfs_ntcreatefile"); - } - write_intent = is_write_or_create(access, disp, 1); - if (!write_intent) { - DirBucket* dir = get_dir_bucket(key, 0); - if (dir != NULL && duplicate_dummy_dir(out)) { - if (iosb != NULL) { - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = 1; - } - register_dir_handle(out ? *out : INVALID_HANDLE_VALUE, dir); - InterlockedIncrement64(&g_redirect_dir); - return STATUS_SUCCESS; - } - } - - if (!should_skip_redirect(access, disp, opts, 1) && - !protected_ext_module(key)) { - VfsEntry* e = lookup_entry(key); - if (e != NULL && e->real_nt != NULL) { - UNICODE_STRING_FL new_us; - OBJECT_ATTRIBUTES_FL new_oa; - if (build_redirect_oa(oa, &new_us, &new_oa, e->real_nt)) { - ++t_inside_redirect; - NTSTATUS st = Real_NtCreateFile(out, access, &new_oa, iosb, - alloc_sz, file_attrs, share, disp, - opts, ea, ea_len); - --t_inside_redirect; - InterlockedIncrement64(&g_redirect_file); - if (st == STATUS_SUCCESS && out != NULL && *out != NULL && - *out != INVALID_HANDLE_VALUE) { - mmap_attach(*out, key); - } - return st; - } - } else if (e == NULL) { - /* Index miss. Only return STATUS_OBJECT_NAME_NOT_FOUND when - * attr_cache has a *confirmed* negative for this key — that - * is, a previous Real_NtQueryAttributesFile probe returned - * ENOENT. A pure cache miss falls through to Real_, because - * the bridge index is not authoritative: backing-relative - * entries can carry real_nt=NULL, runtime-created files - * never reach the index, and other-process writes bypass - * our hooks entirely. Mirrors try_kernel32_attr_hit. */ - InterlockedIncrement64(&g_redirect_miss); - ULONG cattrs = 0; - unsigned long long csize = 0; - FILETIME cmtime; - int cexists = 0; - if (attr_cache_lookup(key, &cattrs, &csize, &cmtime, &cexists) - && !cexists) { - if (iosb != NULL) { - iosb->u.Status = STATUS_OBJECT_NAME_NOT_FOUND; - iosb->Information = 0; - } - InterlockedIncrement64(&g_open_neg); - return STATUS_OBJECT_NAME_NOT_FOUND; - } - } - /* else: entry in index but real_nt==NULL (backing file). Fall - * through to Real_NtCreateFile so Wine + FUSE serve it. */ - } else { - InterlockedIncrement64(&g_redirect_skip); - } - } else { - InterlockedIncrement64(&g_redirect_skip); - } - } - NTSTATUS rs = Real_NtCreateFile(out, access, oa, iosb, alloc_sz, - file_attrs, share, disp, opts, ea, ea_len); - int rs_write_intent = is_write_or_create(access, disp, 1); - if (have_key && rs_write_intent && rs == STATUS_SUCCESS) { - attr_cache_mark_exists(key); - } - if (rs == STATUS_SUCCESS && out != NULL && *out != NULL && - *out != INVALID_HANDLE_VALUE && !rs_write_intent && - !(opts & 0x00000001u /* FILE_DIRECTORY_FILE */)) { - mmap_attach(*out, have_key ? key : NULL); - } - return rs; -} - -static NTSTATUS NTAPI Hook_NtOpenFile(PHANDLE out, ACCESS_MASK access, - POBJECT_ATTRIBUTES_FL oa, - PIO_STATUS_BLOCK_FL iosb, ULONG share, - ULONG opts) -{ - InterlockedIncrement64(&g_nt_open); - if (g_index_ready && t_inside_redirect == 0 && oa != NULL && - oa->ObjectName != NULL && oa->RootDirectory == NULL && - !us_under_mount(oa->ObjectName)) { - return Real_NtOpenFile(out, access, oa, iosb, share, opts); - } - char ofkey[2048]; - ofkey[0] = '\0'; - int of_have_key = 0; - int of_write_intent = is_write_or_create(access, 1u, 0); - if (oa != NULL && oa->ObjectName != NULL && oa->RootDirectory == NULL && - t_inside_redirect == 0 && g_index_ready) { - char* key = ofkey; - if (nt_path_to_key(oa->ObjectName, key, sizeof(ofkey))) { - of_have_key = 1; - if (InterlockedCompareExchange(&g_first_open_logged, 1, 0) == 0) { - log_phase("first_vfs_ntopenfile"); - } - if (!of_write_intent) { - DirBucket* dir = get_dir_bucket(key, 0); - if (dir != NULL && duplicate_dummy_dir(out)) { - if (iosb != NULL) { - iosb->u.Status = STATUS_SUCCESS; - iosb->Information = 1; - } - register_dir_handle(out ? *out : INVALID_HANDLE_VALUE, dir); - InterlockedIncrement64(&g_redirect_dir); - return STATUS_SUCCESS; - } - } - - if (!should_skip_redirect(access, 1u, opts, 0) && - !protected_ext_module(key)) { - VfsEntry* e = lookup_entry(key); - if (e != NULL && e->real_nt != NULL) { - UNICODE_STRING_FL new_us; - OBJECT_ATTRIBUTES_FL new_oa; - if (build_redirect_oa(oa, &new_us, &new_oa, e->real_nt)) { - ++t_inside_redirect; - NTSTATUS st = - Real_NtOpenFile(out, access, &new_oa, iosb, share, opts); - --t_inside_redirect; - InterlockedIncrement64(&g_redirect_file); - if (st == STATUS_SUCCESS && out != NULL && *out != NULL && - *out != INVALID_HANDLE_VALUE) { - mmap_attach(*out, key); - } - return st; - } - } else if (e == NULL) { - /* Index miss — only ENOENT on confirmed-negative attr_cache. - * See Hook_NtCreateFile for the full rationale. */ - InterlockedIncrement64(&g_redirect_miss); - ULONG cattrs = 0; - unsigned long long csize = 0; - FILETIME cmtime; - int cexists = 0; - if (attr_cache_lookup(key, &cattrs, &csize, &cmtime, &cexists) - && !cexists) { - if (iosb != NULL) { - iosb->u.Status = STATUS_OBJECT_NAME_NOT_FOUND; - iosb->Information = 0; - } - InterlockedIncrement64(&g_open_neg); - return STATUS_OBJECT_NAME_NOT_FOUND; - } - } - /* else: entry in index, real_nt==NULL (backing file). Fall - * through to Real_NtOpenFile. */ - } else { - InterlockedIncrement64(&g_redirect_skip); - } - } else { - InterlockedIncrement64(&g_redirect_skip); - } - } - NTSTATUS rs = Real_NtOpenFile(out, access, oa, iosb, share, opts); - if (of_have_key && of_write_intent && rs == STATUS_SUCCESS) { - attr_cache_mark_exists(ofkey); - } - if (rs == STATUS_SUCCESS && out != NULL && *out != NULL && - *out != INVALID_HANDLE_VALUE && !of_write_intent && - !(opts & 0x00000001u /* FILE_DIRECTORY_FILE */)) { - mmap_attach(*out, of_have_key ? ofkey : NULL); - } - return rs; -} - -static NTSTATUS NTAPI Hook_NtClose(HANDLE h) -{ - InterlockedIncrement64(&g_nt_close); - unregister_dir_handle(h); - mmap_detach(h); - return Real_NtClose(h); -} - -static NTSTATUS NTAPI Hook_NtQueryDirectoryFile( - HANDLE h, HANDLE ev, PIO_APC_ROUTINE_FL apc, PVOID apc_ctx, - PIO_STATUS_BLOCK_FL iosb, PVOID info, ULONG len, ULONG info_class, - BOOLEAN single, PUNICODE_STRING_FL file_name, BOOLEAN restart) -{ - InterlockedIncrement64(&g_nt_qdf); - if (g_dir_lock_ready) { - EnterCriticalSection(&g_dir_lock); - DirHandleState* st = find_dir_handle_locked(h); - if (st != NULL) { - NTSTATUS r = serve_dir_query_locked(st, info, len, info_class, - restart ? 1 : 0, - single ? 1 : 0, file_name, iosb); - LeaveCriticalSection(&g_dir_lock); - return r; - } - LeaveCriticalSection(&g_dir_lock); - } - InterlockedIncrement64(&g_dir_query_bypass); - return Real_NtQueryDirectoryFile(h, ev, apc, apc_ctx, iosb, info, len, - info_class, single, file_name, restart); -} - -static NTSTATUS NTAPI Hook_NtQueryDirectoryFileEx( - HANDLE h, HANDLE ev, PIO_APC_ROUTINE_FL apc, PVOID apc_ctx, - PIO_STATUS_BLOCK_FL iosb, PVOID info, ULONG len, ULONG info_class, - ULONG query_flags, PUNICODE_STRING_FL file_name) -{ - InterlockedIncrement64(&g_nt_qdfex); - if (g_dir_lock_ready) { - EnterCriticalSection(&g_dir_lock); - DirHandleState* st = find_dir_handle_locked(h); - if (st != NULL) { - NTSTATUS r = serve_dir_query_locked( - st, info, len, info_class, (query_flags & FL_SL_RESTART_SCAN) != 0, - (query_flags & FL_SL_RETURN_SINGLE_ENTRY) != 0, file_name, iosb); - LeaveCriticalSection(&g_dir_lock); - return r; - } - LeaveCriticalSection(&g_dir_lock); - } - InterlockedIncrement64(&g_dir_query_bypass); - return Real_NtQueryDirectoryFileEx(h, ev, apc, apc_ctx, iosb, info, len, - info_class, query_flags, file_name); -} - -static NTSTATUS NTAPI Hook_NtQueryAttributesFile(POBJECT_ATTRIBUTES_FL oa, - PVOID info) -{ - InterlockedIncrement64(&g_nt_qattr); - if (g_index_ready && t_inside_redirect == 0 && oa != NULL && - oa->ObjectName != NULL && oa->RootDirectory == NULL && - !us_under_mount(oa->ObjectName)) { - return Real_NtQueryAttributesFile(oa, info); - } - if (info != NULL && t_inside_redirect == 0) { - ULONG attrs = 0; - unsigned long long size = 0; - FILETIME mtime; - char key[2048]; - key[0] = '\0'; - if (attr_path_key(oa, key, sizeof(key)) && - query_index_attrs_key(key, &attrs, &size, &mtime)) { - (void)size; - fill_basic_info((FL_FILE_BASIC_INFORMATION*)info, attrs, mtime); - InterlockedIncrement64(&g_attr_hit); - return STATUS_SUCCESS; - } - if (key[0] != '\0') { - ULONG cattrs = 0; - unsigned long long csize = 0; - FILETIME cmtime; - int cexists = 0; - if (attr_cache_lookup(key, &cattrs, &csize, &cmtime, &cexists)) { - InterlockedIncrement64(&g_attr_cache_hit); - if (cexists) { - fill_basic_info((FL_FILE_BASIC_INFORMATION*)info, cattrs, - cmtime); - return STATUS_SUCCESS; - } - return STATUS_OBJECT_NAME_NOT_FOUND; - } - if (should_neg_attr_miss(key)) { - InterlockedIncrement64(&g_attr_miss); - InterlockedIncrement64(&g_attr_neg); - return STATUS_OBJECT_NAME_NOT_FOUND; - } - InterlockedIncrement64(&g_attr_passthru); - NTSTATUS rs = Real_NtQueryAttributesFile(oa, info); - if (rs == STATUS_SUCCESS) { - FL_FILE_BASIC_INFORMATION* bi = - (FL_FILE_BASIC_INFORMATION*)info; - FILETIME ft; - ft.dwLowDateTime = (DWORD)bi->LastWriteTime.LowPart; - ft.dwHighDateTime = (DWORD)bi->LastWriteTime.HighPart; - attr_cache_insert(key, bi->FileAttributes, 0, ft, 1); - } else if (rs == STATUS_OBJECT_NAME_NOT_FOUND) { - FILETIME zft = {0, 0}; - attr_cache_insert(key, 0, 0, zft, 0); - } - return rs; - } - InterlockedIncrement64(&g_attr_passthru); - } - return Real_NtQueryAttributesFile(oa, info); -} - -static NTSTATUS NTAPI Hook_NtQueryFullAttributesFile(POBJECT_ATTRIBUTES_FL oa, - PVOID info) -{ - InterlockedIncrement64(&g_nt_qfullattr); - if (g_index_ready && t_inside_redirect == 0 && oa != NULL && - oa->ObjectName != NULL && oa->RootDirectory == NULL && - !us_under_mount(oa->ObjectName)) { - return Real_NtQueryFullAttributesFile(oa, info); - } - if (info != NULL && t_inside_redirect == 0) { - ULONG attrs = 0; - unsigned long long size = 0; - FILETIME mtime; - char key[2048]; - key[0] = '\0'; - if (attr_path_key(oa, key, sizeof(key)) && - query_index_attrs_key(key, &attrs, &size, &mtime)) { - fill_network_info((FL_FILE_NETWORK_OPEN_INFORMATION*)info, attrs, size, - mtime); - InterlockedIncrement64(&g_attr_hit); - return STATUS_SUCCESS; - } - if (key[0] != '\0') { - ULONG cattrs = 0; - unsigned long long csize = 0; - FILETIME cmtime; - int cexists = 0; - if (attr_cache_lookup(key, &cattrs, &csize, &cmtime, &cexists)) { - InterlockedIncrement64(&g_attr_cache_hit); - if (cexists) { - fill_network_info( - (FL_FILE_NETWORK_OPEN_INFORMATION*)info, cattrs, csize, - cmtime); - return STATUS_SUCCESS; - } - return STATUS_OBJECT_NAME_NOT_FOUND; - } - if (should_neg_attr_miss(key)) { - InterlockedIncrement64(&g_attr_miss); - InterlockedIncrement64(&g_attr_neg); - return STATUS_OBJECT_NAME_NOT_FOUND; - } - InterlockedIncrement64(&g_attr_passthru); - NTSTATUS rs = Real_NtQueryFullAttributesFile(oa, info); - if (rs == STATUS_SUCCESS) { - FL_FILE_NETWORK_OPEN_INFORMATION* ni = - (FL_FILE_NETWORK_OPEN_INFORMATION*)info; - FILETIME ft; - ft.dwLowDateTime = (DWORD)ni->LastWriteTime.LowPart; - ft.dwHighDateTime = (DWORD)ni->LastWriteTime.HighPart; - attr_cache_insert(key, ni->FileAttributes, - (unsigned long long)ni->EndOfFile.QuadPart, ft, - 1); - } else if (rs == STATUS_OBJECT_NAME_NOT_FOUND) { - FILETIME zft = {0, 0}; - attr_cache_insert(key, 0, 0, zft, 0); - } - return rs; - } - InterlockedIncrement64(&g_attr_passthru); - } - return Real_NtQueryFullAttributesFile(oa, info); -} - -static int try_kernel32_attr_hit(LPCWSTR path, GET_FILEEX_INFO_LEVELS level, - LPVOID info) -{ - if (info == NULL || level != GetFileExInfoStandard || - t_inside_redirect != 0 || !g_index_ready) { - return 0; - } - - char key[2048]; - key[0] = '\0'; - if (!dos_wide_path_to_key(path, key, sizeof(key)) || key[0] == '\0') { - return 0; - } - - ULONG attrs = 0; - unsigned long long size = 0; - FILETIME mtime; - if (query_index_attrs_key(key, &attrs, &size, &mtime)) { - fill_win32_attr_data((WIN32_FILE_ATTRIBUTE_DATA*)info, attrs, size, - mtime); - InterlockedIncrement64(&g_k32_attr_hit); - return 1; - } - - ULONG cattrs = 0; - unsigned long long csize = 0; - FILETIME cmtime; - int cexists = 0; - if (attr_cache_lookup(key, &cattrs, &csize, &cmtime, &cexists)) { - InterlockedIncrement64(&g_k32_cache_hit); - if (cexists) { - fill_win32_attr_data((WIN32_FILE_ATTRIBUTE_DATA*)info, cattrs, csize, - cmtime); - return 1; - } - SetLastError(ERROR_FILE_NOT_FOUND); - InterlockedIncrement64(&g_k32_neg); - return -1; - } - - return 0; -} - -static BOOL WINAPI Hook_GetFileAttributesExW(LPCWSTR path, - GET_FILEEX_INFO_LEVELS level, - LPVOID info) -{ - InterlockedIncrement64(&g_k32_gfaexw); - if (g_index_ready && t_inside_redirect == 0 && path != NULL && - !wide_under_mount(path)) { - InterlockedIncrement64(&g_k32_passthru); - return Real_GetFileAttributesExW(path, level, info); - } - int hit = try_kernel32_attr_hit(path, level, info); - if (hit > 0) return TRUE; - if (hit < 0) return FALSE; - InterlockedIncrement64(&g_k32_passthru); - return Real_GetFileAttributesExW(path, level, info); -} - -static BOOL WINAPI Hook_GetFileAttributesExA(LPCSTR path, - GET_FILEEX_INFO_LEVELS level, - LPVOID info) -{ - InterlockedIncrement64(&g_k32_gfaexa); - if (g_index_ready && t_inside_redirect == 0 && path != NULL && - !ansi_under_mount(path)) { - InterlockedIncrement64(&g_k32_passthru); - return Real_GetFileAttributesExA(path, level, info); - } - if (path != NULL && info != NULL && level == GetFileExInfoStandard && - t_inside_redirect == 0 && g_index_ready) { - int wlen = MultiByteToWideChar(CP_ACP, 0, path, -1, NULL, 0); - if (wlen > 0 && wlen < 32760) { - WCHAR* wpath = (WCHAR*)malloc((size_t)wlen * sizeof(WCHAR)); - if (wpath != NULL) { - MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, wlen); - int hit = try_kernel32_attr_hit(wpath, level, info); - free(wpath); - if (hit > 0) return TRUE; - if (hit < 0) return FALSE; - } - } - } - InterlockedIncrement64(&g_k32_passthru); - return Real_GetFileAttributesExA(path, level, info); -} - -static int hook_api(const wchar_t* module, const char* name, void* hook, - void** real, int required) -{ - MH_STATUS st = MH_CreateHookApi(module, name, hook, real); - log_hook_status(name, st); - if (st != MH_OK && st != MH_ERROR_ALREADY_CREATED) { - if (!required) return 1; - return 0; - } - return 1; -} - -static DWORD WINAPI hook_worker(void* unused) -{ - (void)unused; - g_phase_start_tick = GetTickCount64(); - log_phase("hook_worker_entry"); - if (!load_index()) { - log_msg("ntdll hooks skipped: index unavailable"); - return 0; - } - log_phase("index_loaded"); - InitializeCriticalSection(&g_dir_lock); - g_dir_lock_ready = 1; - attr_cache_init(); - mmap_init(); - ensure_dummy_dir(); - - MH_STATUS st = MH_Initialize(); - if (st != MH_OK && st != MH_ERROR_ALREADY_INITIALIZED) { - log_msg("MinHook init failed"); - return 0; - } - - int ok = 1; - ok &= hook_api(L"ntdll.dll", "NtCreateFile", Hook_NtCreateFile, - (void**)&Real_NtCreateFile, 1); - ok &= hook_api(L"ntdll.dll", "NtOpenFile", Hook_NtOpenFile, - (void**)&Real_NtOpenFile, 1); - ok &= hook_api(L"ntdll.dll", "NtClose", Hook_NtClose, - (void**)&Real_NtClose, 1); - ok &= hook_api(L"ntdll.dll", "NtQueryDirectoryFile", - Hook_NtQueryDirectoryFile, - (void**)&Real_NtQueryDirectoryFile, 1); - ok &= hook_api(L"ntdll.dll", "NtQueryDirectoryFileEx", - Hook_NtQueryDirectoryFileEx, - (void**)&Real_NtQueryDirectoryFileEx, 0); - ok &= hook_api(L"ntdll.dll", "NtQueryAttributesFile", - Hook_NtQueryAttributesFile, - (void**)&Real_NtQueryAttributesFile, 1); - ok &= hook_api(L"ntdll.dll", "NtQueryFullAttributesFile", - Hook_NtQueryFullAttributesFile, - (void**)&Real_NtQueryFullAttributesFile, 1); - ok &= hook_api(L"ntdll.dll", "NtReadFile", Hook_NtReadFile, - (void**)&Real_NtReadFile, 1); - ok &= hook_api(L"ntdll.dll", "NtSetInformationFile", - Hook_NtSetInformationFile, - (void**)&Real_NtSetInformationFile, 1); - ok &= hook_api(L"ntdll.dll", "NtQueryInformationFile", - Hook_NtQueryInformationFile, - (void**)&Real_NtQueryInformationFile, 1); - ok &= hook_api(L"kernel32.dll", "GetFileAttributesExW", - Hook_GetFileAttributesExW, - (void**)&Real_GetFileAttributesExW, 0); - ok &= hook_api(L"kernel32.dll", "GetFileAttributesExA", - Hook_GetFileAttributesExA, - (void**)&Real_GetFileAttributesExA, 0); - if (!ok) { - log_msg("ntdll hook create failed"); - return 0; - } - - st = MH_EnableHook(MH_ALL_HOOKS); - if (st != MH_OK) { - log_msg("ntdll hook enable failed"); - return 0; - } - log_phase("hooks_enabled"); - - InterlockedExchange(&g_installed, 1); - log_msg("ntdll hooks installed (file+dir+attr redirect)"); - start_stats_worker(); - return 0; -} - -void fluorine_vfs_start_hooks(void) -{ - if (InterlockedExchange(&g_started, 1) != 0) return; - HANDLE th = CreateThread(NULL, 0, hook_worker, NULL, 0, NULL); - if (th != NULL) CloseHandle(th); -} - -void fluorine_vfs_report_hooks(void) -{ - if (g_installed) log_stats(); -} diff --git a/src/src/vfs/fluorine_vfs_inject.c b/src/src/vfs/fluorine_vfs_inject.c deleted file mode 100644 index 8c7b2d1..0000000 --- a/src/src/vfs/fluorine_vfs_inject.c +++ /dev/null @@ -1,310 +0,0 @@ -// PE-side VFS injector for Wine/Proton. -// -// Loaded into PE game processes via Wine's AppInit_DLLs registry mechanism -// when available, or through the game-directory hid.dll proxy fallback. The -// hook implementation short-circuits selected NTDLL file, directory, read, and -// metadata calls against Fluorine's exported VFS bridge index. -// -// Design constraints: -// - Hot path runs in every PE process spawned in the prefix. -// Keep DllMain cheap; bail early in known non-game host procs. -// - AppInit_DLLs DLLs are loaded *from inside user32's DllMain*, so -// the Windows loader lock is held when our DllMain runs. Calling -// into user32 (e.g. wsprintfA) or any other DLL whose own DllMain -// hasn't run yet causes ShellExecuteEx-style "General failure" -// across every PE process in the prefix. Stick to kernel32-only -// APIs and inline-format anything we need to print. -// - AppInit_DLLs DLLs also load before MSVCRT stdio is initialized. -// Use Win32 (GetEnvironmentVariableA / WriteFile / GetStdHandle) -// instead of fprintf/getenv so we don't crash on uninitialized -// CRT state in early-stage processes. -// - Talk to Fluorine via env vars set by ProtonLauncher -// (FLUORINE_VFS_INDEX, FLUORINE_VFS_DATA_DIR, FLUORINE_VFS_MOUNT). - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -void fluorine_vfs_start_hooks(void); -void fluorine_vfs_report_hooks(void); - -static int ascii_eq_ci(const char* a, const char* b) -{ - while (*a && *b) { - char ca = *a, cb = *b; - if (ca >= 'A' && ca <= 'Z') ca = (char)(ca + 32); - if (cb >= 'A' && cb <= 'Z') cb = (char)(cb + 32); - if (ca != cb) return 0; - ++a; ++b; - } - return *a == '\0' && *b == '\0'; -} - -static int env_truthy(const char* name) -{ - char buf[16]; - DWORD n = GetEnvironmentVariableA(name, buf, (DWORD)sizeof(buf)); - if (n == 0 || n >= sizeof(buf)) return 0; - if (buf[0] == '0' && buf[1] == '\0') return 0; - if (ascii_eq_ci(buf, "false")) return 0; - if (ascii_eq_ci(buf, "no")) return 0; - return 1; -} - -static DWORD env_get(const char* name, char* out, DWORD cap) -{ - DWORD n = GetEnvironmentVariableA(name, out, cap); - if (n == 0 || n >= cap) { - if (cap > 0) out[0] = '\0'; - return 0; - } - return n; -} - -static const char* basename_of(const char* path) -{ - const char* p = path; - const char* last = path; - while (*p) { - if (*p == '\\' || *p == '/') last = p + 1; - ++p; - } - return last; -} - -// Skip non-game host processes that fork/exec dozens of times during a -// Wine/Proton session. Hooking them costs more than it saves and -// occasionally breaks pipes (services.exe spawns PE helpers inline). -static int is_skipped_host_proc(const char* exe_basename) -{ - static const char* const skip[] = { - "services.exe", "plugplay.exe", "winedevice.exe", "rpcss.exe", - "explorer.exe", "svchost.exe", "conhost.exe", "wineboot.exe", - "winemenubuilder.exe", "tabtip.exe", "start.exe", "regedit.exe", - "rundll32.exe", "msiexec.exe", - NULL, - }; - for (size_t i = 0; skip[i] != NULL; ++i) { - if (ascii_eq_ci(exe_basename, skip[i])) return 1; - } - return 0; -} - -static void log_line(const char* buf, DWORD len) -{ - HANDLE h = GetStdHandle(STD_ERROR_HANDLE); - if (h != NULL && h != INVALID_HANDLE_VALUE) { - DWORD wrote = 0; - WriteFile(h, buf, len, &wrote, NULL); - } - - // Stderr can disappear under some Proton launch paths. Keep this - // kernel32-only fallback so Phase 1 can distinguish "not loaded" from - // "loaded, but the launch log swallowed stderr". - HANDLE f = CreateFileA("Z:\\tmp\\fluorine_vfs_appinit.log", - FILE_APPEND_DATA, - FILE_SHARE_READ | FILE_SHARE_WRITE | - FILE_SHARE_DELETE, - NULL, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (f != INVALID_HANDLE_VALUE) { - DWORD wrote = 0; - WriteFile(f, buf, len, &wrote, NULL); - CloseHandle(f); - } -} - -static DWORD copy_str(char* dst, DWORD dst_pos, DWORD dst_cap, const char* src) -{ - while (*src && dst_pos + 1 < dst_cap) dst[dst_pos++] = *src++; - return dst_pos; -} - -#ifdef FLUORINE_VFS_PROXY_HID -static HMODULE real_hid(void) -{ - static HMODULE mod = NULL; - if (mod == NULL) { - mod = LoadLibraryA("C:\\windows\\system32\\hid.dll"); - } - return mod; -} - -static FARPROC real_hid_proc(const char* name) -{ - HMODULE mod = real_hid(); - if (mod == NULL) return NULL; - return GetProcAddress(mod, name); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_FreePreparsedData(void* preparsed) -{ - typedef BOOLEAN(WINAPI * Fn)(void*); - Fn fn = (Fn)real_hid_proc("HidD_FreePreparsedData"); - if (fn == NULL) return FALSE; - return fn(preparsed); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetAttributes(HANDLE file, void* attrs) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*); - Fn fn = (Fn)real_hid_proc("HidD_GetAttributes"); - if (fn == NULL) return FALSE; - return fn(file, attrs); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetFeature(HANDLE file, - void* report, - ULONG len) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*, ULONG); - Fn fn = (Fn)real_hid_proc("HidD_GetFeature"); - if (fn == NULL) return FALSE; - return fn(file, report, len); -} - -__declspec(dllexport) void WINAPI HidD_GetHidGuid(GUID* guid) -{ - typedef void(WINAPI * Fn)(GUID*); - Fn fn = (Fn)real_hid_proc("HidD_GetHidGuid"); - if (fn != NULL) fn(guid); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetManufacturerString(HANDLE file, - void* buf, - ULONG len) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*, ULONG); - Fn fn = (Fn)real_hid_proc("HidD_GetManufacturerString"); - if (fn == NULL) return FALSE; - return fn(file, buf, len); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetPreparsedData(HANDLE file, - void** preparsed) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void**); - Fn fn = (Fn)real_hid_proc("HidD_GetPreparsedData"); - if (fn == NULL) return FALSE; - return fn(file, preparsed); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetProductString(HANDLE file, - void* buf, - ULONG len) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*, ULONG); - Fn fn = (Fn)real_hid_proc("HidD_GetProductString"); - if (fn == NULL) return FALSE; - return fn(file, buf, len); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_GetSerialNumberString(HANDLE file, - void* buf, - ULONG len) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*, ULONG); - Fn fn = (Fn)real_hid_proc("HidD_GetSerialNumberString"); - if (fn == NULL) return FALSE; - return fn(file, buf, len); -} - -__declspec(dllexport) BOOLEAN WINAPI HidD_SetFeature(HANDLE file, - void* report, - ULONG len) -{ - typedef BOOLEAN(WINAPI * Fn)(HANDLE, void*, ULONG); - Fn fn = (Fn)real_hid_proc("HidD_SetFeature"); - if (fn == NULL) return FALSE; - return fn(file, report, len); -} - -__declspec(dllexport) LONG WINAPI HidP_GetCaps(void* preparsed, void* caps) -{ - typedef LONG(WINAPI * Fn)(void*, void*); - Fn fn = (Fn)real_hid_proc("HidP_GetCaps"); - if (fn == NULL) return (LONG)0xc0110001; - return fn(preparsed, caps); -} - -__declspec(dllexport) LONG WINAPI HidP_GetValueCaps(int report_type, - void* caps, - ULONG* len, - void* preparsed) -{ - typedef LONG(WINAPI * Fn)(int, void*, ULONG*, void*); - Fn fn = (Fn)real_hid_proc("HidP_GetValueCaps"); - if (fn == NULL) return (LONG)0xc0110001; - return fn(report_type, caps, len, preparsed); -} -#endif - -BOOL APIENTRY DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved) -{ - (void)reserved; - if (reason == DLL_PROCESS_DETACH) { - fluorine_vfs_report_hooks(); - return TRUE; - } - if (reason != DLL_PROCESS_ATTACH) return TRUE; - DisableThreadLibraryCalls(hinst); - - if (env_truthy("FLUORINE_DISABLE_VFS_INJECT")) return TRUE; - - char proc_path[MAX_PATH]; - DWORD pn = GetModuleFileNameA(NULL, proc_path, (DWORD)sizeof(proc_path)); - if (pn == 0 || pn >= sizeof(proc_path)) { - proc_path[0] = '\0'; - } else { - proc_path[pn] = '\0'; - } - const char* exe = basename_of(proc_path); - - if (is_skipped_host_proc(exe)) return TRUE; - - char idx[1024]; - env_get("FLUORINE_VFS_INDEX", idx, (DWORD)sizeof(idx)); - char mount[1024]; - env_get("FLUORINE_VFS_MOUNT", mount, (DWORD)sizeof(mount)); - - // Log process/env wiring before hook startup. This also leaves a marker in - // /tmp/fluorine_vfs_appinit.log when Proton swallows stderr. - if (idx[0] != '\0' || env_truthy("FLUORINE_VFS_INJECT_DEBUG")) { - char line[2048]; - DWORD pos = 0; - pos = copy_str(line, pos, sizeof(line), "[fluorine_vfs] pid="); - // Format PID without calling into user32 (wsprintfA) — see header - // comment about loader-lock constraint when running from - // AppInit_DLLs. - char num[16]; - DWORD pid = GetCurrentProcessId(); - int ndigits = 0; - if (pid == 0) { - num[ndigits++] = '0'; - } else { - char rev[16]; - int rn = 0; - while (pid > 0 && rn < (int)sizeof(rev)) { - rev[rn++] = (char)('0' + (pid % 10)); - pid /= 10; - } - while (rn > 0) num[ndigits++] = rev[--rn]; - } - for (int i = 0; i < ndigits && pos + 1 < sizeof(line); ++i) line[pos++] = num[i]; - pos = copy_str(line, pos, sizeof(line), " proc='"); - pos = copy_str(line, pos, sizeof(line), exe[0] ? exe : "?"); - pos = copy_str(line, pos, sizeof(line), "' index='"); - pos = copy_str(line, pos, sizeof(line), idx[0] ? idx : "<unset>"); - pos = copy_str(line, pos, sizeof(line), "' mount='"); - pos = copy_str(line, pos, sizeof(line), mount[0] ? mount : "<unset>"); - pos = copy_str(line, pos, sizeof(line), "'\n"); - log_line(line, pos); - } - - if (idx[0] != '\0') { - fluorine_vfs_start_hooks(); - } - - return TRUE; -} diff --git a/src/src/vfs/vfsbridge.cpp b/src/src/vfs/vfsbridge.cpp deleted file mode 100644 index c74259f..0000000 --- a/src/src/vfs/vfsbridge.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include "vfsbridge.h" - -#include "../fluorinepaths.h" - -#include <algorithm> -#include <chrono> -#include <cstdint> -#include <cstdio> -#include <fstream> -#include <iomanip> -#include <ostream> -#include <sstream> -#include <system_error> - -namespace -{ -namespace fs = std::filesystem; - -uint64_t hash64(const std::string& s) -{ - uint64_t h = 1469598103934665603ULL; - for (unsigned char c : s) { - h ^= c; - h *= 1099511628211ULL; - } - return h; -} - -std::string jsonEscape(const std::string& s) -{ - std::ostringstream out; - out << '"'; - for (unsigned char c : s) { - switch (c) { - case '"': - out << "\\\""; - break; - case '\\': - out << "\\\\"; - break; - case '\b': - out << "\\b"; - break; - case '\f': - out << "\\f"; - break; - case '\n': - out << "\\n"; - break; - case '\r': - out << "\\r"; - break; - case '\t': - out << "\\t"; - break; - default: - if (c < 0x20) { - out << "\\u" << std::hex << std::setw(4) << std::setfill('0') - << static_cast<int>(c) << std::dec; - } else { - out << static_cast<char>(c); - } - break; - } - } - out << '"'; - return out.str(); -} - -int64_t timeToNs(std::chrono::system_clock::time_point t) -{ - return std::chrono::duration_cast<std::chrono::nanoseconds>( - t.time_since_epoch()) - .count(); -} - -int64_t nowToNs() -{ - return timeToNs(std::chrono::system_clock::now()); -} - -void writeMeta(std::ostream& out, const VfsTree& tree, - const std::string& data_dir, const std::string& overwrite_dir, - const std::string& mount_point) -{ - out << "{\"record\":\"meta\"," - << "\"schema\":\"fluorine-vfs-index-v1\"," - << "\"data_dir\":" << jsonEscape(data_dir) << ',' - << "\"overwrite_dir\":" << jsonEscape(overwrite_dir) << ',' - << "\"mount_point\":" << jsonEscape(mount_point) << ',' - << "\"file_count\":" << tree.file_count << ',' - << "\"dir_count\":" << tree.dir_count << ',' - << "\"export_time_ns\":" << nowToNs() << "}\n"; -} - -void writeEntry(std::ostream& out, const std::string& virtual_path, - const VfsNode& node) -{ - out << "{\"record\":\"entry\"," - << "\"type\":" << jsonEscape(node.is_directory ? "directory" : "file") - << ",\"virtual_path\":" << jsonEscape(virtual_path); - - if (!node.is_directory) { - const auto& fi = node.file_info; - out << ",\"real_path\":" << jsonEscape(fi.real_path) - << ",\"real_path_kind\":" - << jsonEscape(fi.is_backing ? "backing_relative" : "absolute") - << ",\"origin\":" << jsonEscape(fi.origin) - << ",\"size\":" << fi.size - << ",\"mtime_ns\":" << timeToNs(fi.mtime) - << ",\"is_backing\":" << (fi.is_backing ? "true" : "false"); - } - - out << "}\n"; -} - -std::size_t writeNode(std::ostream& out, const VfsNode& node, - const std::string& virtual_path) -{ - std::size_t written = 0; - if (!virtual_path.empty()) { - writeEntry(out, virtual_path, node); - ++written; - } - - if (!node.is_directory) { - return written; - } - - auto children = node.listChildren(); - std::sort(children.begin(), children.end(), - [](const auto& a, const auto& b) { - return normalizeForLookup(a.first) < normalizeForLookup(b.first); - }); - - for (const auto& [displayName, child] : children) { - if (child == nullptr) { - continue; - } - std::string childPath = virtual_path; - if (!childPath.empty()) { - childPath += '/'; - } - childPath += displayName; - written += writeNode(out, *child, childPath); - } - - return written; -} - -} // namespace - -fs::path vfsBridgeIndexPath( - const std::string& data_dir, const std::string& overwrite_dir, - const std::vector<std::pair<std::string, std::string>>& mods) -{ - std::string fp; - fp.reserve(4096); - fp += data_dir; - fp += '\0'; - fp += overwrite_dir; - fp += '\0'; - for (const auto& [name, path] : mods) { - fp += name; - fp += '\0'; - fp += path; - fp += '\0'; - } - - char buf[32]; - std::snprintf(buf, sizeof(buf), "%016llx.jsonl", - static_cast<unsigned long long>(hash64(fp))); - - return fs::path(fluorineVfsBridgeDir().toStdString()) / buf; -} - -VfsBridgeExportResult exportVfsBridgeIndex( - const VfsTree& tree, const fs::path& path, const std::string& data_dir, - const std::string& overwrite_dir, const std::string& mount_point) -{ - VfsBridgeExportResult result; - result.path = path; - - std::error_code ec; - fs::create_directories(path.parent_path(), ec); - if (ec) { - result.error = ec.message(); - return result; - } - - fs::path tmp = path; - tmp += ".tmp"; - - { - std::ofstream out(tmp, std::ios::trunc); - if (!out) { - result.error = "failed to open temporary index file"; - return result; - } - - writeMeta(out, tree, data_dir, overwrite_dir, mount_point); - result.records_written = writeNode(out, tree.root, {}); - - out.flush(); - if (!out.good()) { - result.error = "failed while writing index file"; - fs::remove(tmp, ec); - return result; - } - } - - fs::rename(tmp, path, ec); - if (ec) { - fs::remove(path, ec); - ec.clear(); - fs::rename(tmp, path, ec); - if (ec) { - result.error = ec.message(); - fs::remove(tmp, ec); - return result; - } - } - - result.ok = true; - return result; -} diff --git a/src/src/vfs/vfsbridge.h b/src/src/vfs/vfsbridge.h deleted file mode 100644 index 34b6880..0000000 --- a/src/src/vfs/vfsbridge.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef VFS_VFSBRIDGE_H -#define VFS_VFSBRIDGE_H - -#include "vfstree.h" - -#include <filesystem> -#include <string> -#include <utility> -#include <vector> - -struct VfsBridgeExportResult -{ - bool ok = false; - std::filesystem::path path; - std::size_t records_written = 0; - std::string error; -}; - -std::filesystem::path vfsBridgeIndexPath( - const std::string& data_dir, const std::string& overwrite_dir, - const std::vector<std::pair<std::string, std::string>>& mods); - -VfsBridgeExportResult exportVfsBridgeIndex( - const VfsTree& tree, const std::filesystem::path& path, - const std::string& data_dir, const std::string& overwrite_dir, - const std::string& mount_point); - -#endif // VFS_VFSBRIDGE_H diff --git a/src/src/vfs/vfsbridgepreload.c b/src/src/vfs/vfsbridgepreload.c deleted file mode 100644 index daaa5d9..0000000 --- a/src/src/vfs/vfsbridgepreload.c +++ /dev/null @@ -1,1872 +0,0 @@ -#define _GNU_SOURCE - -#include <ctype.h> -#include <dirent.h> -#include <dlfcn.h> -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <linux/stat.h> -#include <pthread.h> -#include <stdarg.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <unistd.h> - -typedef struct Entry -{ - char* key; - char* real_path; - int directory; - int backing; - uint64_t size; - int64_t mtime_ns; - uint32_t child_start; - uint32_t child_count; -} Entry; - -static Entry* g_entries = NULL; -static size_t g_entry_count = 0; -static size_t g_entry_cap = 0; -static uint32_t* g_children = NULL; -static size_t g_children_count = 0; -static char* g_mount_point = NULL; -static char* g_data_dir = NULL; -static int g_loaded = 0; -static int g_load_failed = 0; -static int g_debug = 0; -static int g_stats = 0; -// Directory synthesis (libc opendir + open(O_DIRECTORY) → synthetic FlDir fd). -// Default ON. Set FLUORINE_VFS_PRELOAD_DIRS=0 to disable when debugging Wine -// readdir issues (currently NTDLL bypasses our syscall() hook for getdents64 -// so synthetic dirs read empty there). -static int g_dirs_enabled = 1; -static pthread_once_t g_once = PTHREAD_ONCE_INIT; -static __thread int g_inside = 0; -static uint64_t g_resolve_hits = 0; -static uint64_t g_resolve_misses = 0; -static uint64_t g_open_hits = 0; -static uint64_t g_open_fallbacks = 0; -static uint64_t g_stat_hits = 0; -static uint64_t g_access_hits = 0; -static uint64_t g_open_calls = 0; -static uint64_t g_stat_calls = 0; -static uint64_t g_access_calls = 0; -static uint64_t g_statx_calls = 0; -static uint64_t g_syscall_calls = 0; -static uint64_t g_path_mismatches = 0; -static uint64_t g_path_samples = 0; -static uint64_t g_canon_attempts = 0; -static uint64_t g_canon_mount_hits = 0; -static uint64_t g_canon_outside = 0; -static uint64_t g_canon_failed = 0; -static uint64_t g_opendir_calls = 0; -static uint64_t g_opendir_hits = 0; -static uint64_t g_readdir_calls = 0; -static uint64_t g_readdir_hits = 0; -static uint64_t g_getdents_calls = 0; -static uint64_t g_getdents_hits = 0; -// libc getdents64() symbol path. Wine 9+ / glibc 2.30+ may call this directly -// instead of going through syscall(SYS_getdents64, ...). Keeping the counters -// separate so we can tell which path NTDLL actually uses. -static uint64_t g_getdents_libc_calls = 0; -static uint64_t g_getdents_libc_hits = 0; - -#define FL_DIR_MAGIC ((uint64_t)0xF10DD1ECDEADC0DEULL) - -typedef struct FlDir -{ - uint64_t magic; // first field: type discrimination - size_t pos; - size_t entry_idx; // directory entry in g_entries - int real_fd; // backing /dev/null fd or -1 - struct dirent dirent_buf; - struct dirent64 dirent64_buf; -} FlDir; - -// fd → FlDir* map for getdents64 syscall hook. Open-addressed linear-probe -// hash table. Sized at first insert; bounded by /proc/sys/fs/file-max but in -// practice this stays under ~50 entries per process during a launch. -typedef struct FdSlot -{ - int fd; // -1 = empty - FlDir* dir; -} FdSlot; - -static FdSlot* g_fd_map = NULL; -static size_t g_fd_map_cap = 0; -static size_t g_fd_map_count = 0; -static pthread_mutex_t g_fd_map_mutex = PTHREAD_MUTEX_INITIALIZER; - -static void ensure_loaded(void); - -static void* next_symbol(const char* name) -{ - return dlsym(RTLD_NEXT, name); -} - -static int flag_enabled(const char* name) -{ - const char* v = getenv(name); - if (v == NULL) return 0; - return strcmp(v, "1") == 0 || strcasecmp(v, "true") == 0 || - strcasecmp(v, "yes") == 0; -} - -static char* xstrdup_range(const char* start, size_t len) -{ - char* out = (char*)malloc(len + 1); - if (out == NULL) return NULL; - memcpy(out, start, len); - out[len] = '\0'; - return out; -} - -static char* trim_trailing_slash_dup(const char* s) -{ - if (s == NULL) return NULL; - size_t len = strlen(s); - while (len > 1 && s[len - 1] == '/') --len; - return xstrdup_range(s, len); -} - -static char* normalized_slashes_dup(const char* s) -{ - char* out = trim_trailing_slash_dup(s); - if (out == NULL) return NULL; - for (char* p = out; *p; ++p) { - if (*p == '\\') *p = '/'; - } - while (out[0] == '/' && out[1] == '/') { - memmove(out, out + 1, strlen(out)); - } - return out; -} - -static char* wine_dos_path_to_unix_dup(const char* s) -{ - if (s == NULL) return NULL; - const char* p = s; - if (strncmp(p, "\\\\??\\", 5) == 0) p += 5; - if (strncmp(p, "\\??\\", 4) == 0) p += 4; - if (strncmp(p, "\\\\?\\", 4) == 0) p += 4; - - if (isalpha((unsigned char)p[0]) && p[1] == ':') { - const char drive = (char)tolower((unsigned char)p[0]); - p += 2; - while (*p == '\\' || *p == '/') ++p; - - if (drive == 'z') { - const size_t len = strlen(p) + 2; - char* out = (char*)malloc(len); - if (out == NULL) return NULL; - out[0] = '/'; - strcpy(out + 1, p); - for (char* c = out; *c; ++c) { - if (*c == '\\') *c = '/'; - } - while (strlen(out) > 1 && out[strlen(out) - 1] == '/') { - out[strlen(out) - 1] = '\0'; - } - return out; - } - } - - return NULL; -} - -static void lowercase_vfs_path(char* s) -{ - char* r = s; - char* w = s; - while (*r == '/') ++r; - while (*r) { - char c = *r++; - if (c == '\\') c = '/'; - if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); - *w++ = c; - } - while (w > s && w[-1] == '/') --w; - *w = '\0'; -} - -static char* json_string_field(const char* line, const char* key) -{ - char needle[128]; - snprintf(needle, sizeof(needle), "\"%s\":", key); - const char* p = strstr(line, needle); - if (p == NULL) return NULL; - p = strchr(p + strlen(needle), '"'); - if (p == NULL) return NULL; - ++p; - - size_t cap = strlen(p) + 1; - char* out = (char*)malloc(cap); - if (out == NULL) return NULL; - size_t n = 0; - - while (*p) { - char c = *p++; - if (c == '"') { - out[n] = '\0'; - return out; - } - if (c == '\\' && *p) { - char e = *p++; - switch (e) { - case '"': c = '"'; break; - case '\\': c = '\\'; break; - case '/': c = '/'; break; - case 'b': c = '\b'; break; - case 'f': c = '\f'; break; - case 'n': c = '\n'; break; - case 'r': c = '\r'; break; - case 't': c = '\t'; break; - default: c = e; break; - } - } - out[n++] = c; - } - - free(out); - return NULL; -} - -static int json_int_field(const char* line, const char* key, int64_t* out) -{ - char needle[128]; - snprintf(needle, sizeof(needle), "\"%s\":", key); - const char* p = strstr(line, needle); - if (p == NULL) return 0; - p += strlen(needle); - char* end = NULL; - long long v = strtoll(p, &end, 10); - if (end == p) return 0; - *out = (int64_t)v; - return 1; -} - -static int json_bool_field(const char* line, const char* key) -{ - char needle[128]; - snprintf(needle, sizeof(needle), "\"%s\":", key); - const char* p = strstr(line, needle); - if (p == NULL) return 0; - p += strlen(needle); - return strncmp(p, "true", 4) == 0; -} - -static int add_entry(Entry e) -{ - if (g_entry_count == g_entry_cap) { - size_t next_cap = g_entry_cap == 0 ? 4096 : g_entry_cap * 2; - Entry* next = (Entry*)realloc(g_entries, next_cap * sizeof(Entry)); - if (next == NULL) return 0; - g_entries = next; - g_entry_cap = next_cap; - } - g_entries[g_entry_count++] = e; - return 1; -} - -static int entry_cmp(const void* a, const void* b) -{ - const Entry* ea = (const Entry*)a; - const Entry* eb = (const Entry*)b; - return strcmp(ea->key, eb->key); -} - -static Entry* find_entry(const char* key) -{ - Entry probe; - probe.key = (char*)key; - return (Entry*)bsearch(&probe, g_entries, g_entry_count, sizeof(Entry), entry_cmp); -} - -// Returns index in g_entries of the parent of `key`, or SIZE_MAX if none. -// Only valid after qsort. `key` must not contain trailing slash. -static size_t parent_index(const char* key) -{ - if (key == NULL || key[0] == '\0') return SIZE_MAX; - const char* slash = strrchr(key, '/'); - if (slash == NULL) { - // Top-level key: parent is root entry "". - Entry probe; - probe.key = (char*)""; - Entry* p = (Entry*)bsearch(&probe, g_entries, g_entry_count, sizeof(Entry), entry_cmp); - if (p == NULL) return SIZE_MAX; - return (size_t)(p - g_entries); - } - size_t parent_len = (size_t)(slash - key); - char* parent_key = (char*)malloc(parent_len + 1); - if (parent_key == NULL) return SIZE_MAX; - memcpy(parent_key, key, parent_len); - parent_key[parent_len] = '\0'; - - Entry probe; - probe.key = parent_key; - Entry* p = (Entry*)bsearch(&probe, g_entries, g_entry_count, sizeof(Entry), entry_cmp); - free(parent_key); - if (p == NULL) return SIZE_MAX; - return (size_t)(p - g_entries); -} - -static void build_child_index(void) -{ - if (g_entry_count == 0) return; - - // Pass 1: count children per parent. - for (size_t i = 0; i < g_entry_count; ++i) { - size_t pi = parent_index(g_entries[i].key); - if (pi == SIZE_MAX) continue; - ++g_entries[pi].child_count; - } - - // Pass 2: prefix-sum to compute child_start. - size_t total = 0; - for (size_t i = 0; i < g_entry_count; ++i) { - g_entries[i].child_start = (uint32_t)total; - total += g_entries[i].child_count; - g_entries[i].child_count = 0; // refilled in pass 3 - } - - if (total == 0) return; - g_children = (uint32_t*)malloc(total * sizeof(uint32_t)); - if (g_children == NULL) { - g_load_failed = 1; - return; - } - g_children_count = total; - - // Pass 3: append child indices. - for (size_t i = 0; i < g_entry_count; ++i) { - size_t pi = parent_index(g_entries[i].key); - if (pi == SIZE_MAX) continue; - Entry* parent = &g_entries[pi]; - g_children[parent->child_start + parent->child_count++] = (uint32_t)i; - } -} - -static void load_index(void) -{ - g_inside = 1; - g_debug = flag_enabled("FLUORINE_VFS_BRIDGE_DEBUG"); - g_stats = flag_enabled("FLUORINE_VFS_PRELOAD_STATS"); - // Default-on: disable only when env explicitly says 0/false/no. - const char* dirs_env = getenv("FLUORINE_VFS_PRELOAD_DIRS"); - if (dirs_env != NULL && - (strcmp(dirs_env, "0") == 0 || strcasecmp(dirs_env, "false") == 0 || - strcasecmp(dirs_env, "no") == 0)) { - g_dirs_enabled = 0; - } - - const char* index = getenv("FLUORINE_VFS_INDEX"); - if (index == NULL || index[0] == '\0') { - g_inside = 0; - return; - } - - FILE* f = fopen(index, "r"); - if (f == NULL) { - g_load_failed = 1; - g_inside = 0; - return; - } - - char* line = NULL; - size_t line_cap = 0; - while (getline(&line, &line_cap, f) > 0) { - char* record = json_string_field(line, "record"); - if (record == NULL) continue; - - if (strcmp(record, "meta") == 0) { - char* mount = json_string_field(line, "mount_point"); - char* data = json_string_field(line, "data_dir"); - free(g_mount_point); - free(g_data_dir); - g_mount_point = trim_trailing_slash_dup(mount); - g_data_dir = trim_trailing_slash_dup(data); - free(mount); - free(data); - free(record); - continue; - } - - if (strcmp(record, "entry") != 0) { - free(record); - continue; - } - free(record); - - char* type = json_string_field(line, "type"); - char* key = json_string_field(line, "virtual_path"); - if (type == NULL || key == NULL) { - free(type); - free(key); - continue; - } - lowercase_vfs_path(key); - - Entry e; - memset(&e, 0, sizeof(e)); - e.key = key; - e.directory = strcmp(type, "directory") == 0; - free(type); - - if (!e.directory) { - int64_t size = 0; - e.real_path = json_string_field(line, "real_path"); - e.backing = json_bool_field(line, "is_backing"); - if (json_int_field(line, "size", &size) && size >= 0) { - e.size = (uint64_t)size; - } - json_int_field(line, "mtime_ns", &e.mtime_ns); - } - - if (!add_entry(e)) { - free(e.key); - free(e.real_path); - g_load_failed = 1; - break; - } - } - - free(line); - fclose(f); - - if (g_mount_point != NULL) { - Entry root; - memset(&root, 0, sizeof(root)); - root.key = strdup(""); - root.directory = 1; - if (root.key != NULL) add_entry(root); - } - - qsort(g_entries, g_entry_count, sizeof(Entry), entry_cmp); - build_child_index(); - g_loaded = g_mount_point != NULL && !g_load_failed; - if (g_debug) { - fprintf(stderr, - "[fluorine-vfs-preload] pid=%ld loaded=%d entries=%zu children=%zu index='%s' mount='%s'\n", - (long)getpid(), g_loaded, g_entry_count, g_children_count, index, - g_mount_point ? g_mount_point : ""); - } - g_inside = 0; -} - -static void ensure_loaded(void) -{ - pthread_once(&g_once, load_index); -} - -static void sample_path_mismatch(const char* reason, int dirfd, const char* raw, - const char* clean, const char* canon) -{ - __sync_fetch_and_add(&g_path_mismatches, 1); - if (!g_stats && !g_debug) return; - const uint64_t slot = __sync_fetch_and_add(&g_path_samples, 1); - if (slot >= 24) return; - - fprintf(stderr, - "[fluorine-vfs-preload] path_miss pid=%ld reason=%s dirfd=%d raw='%s' clean='%s' canon='%s' mount='%s'\n", - (long)getpid(), reason ? reason : "", dirfd, - raw ? raw : "", clean ? clean : "", canon ? canon : "", - g_mount_point ? g_mount_point : ""); -} - -// realpath() existing prefix; preserve trailing suffix that does not yet exist. -// Returns malloc'd canonical path or NULL when no parent component resolves. -static char* canonicalize_existing_prefix_dup(const char* path) -{ - if (path == NULL || path[0] != '/') return NULL; - - char* canon = realpath(path, NULL); - if (canon != NULL) return canon; - - size_t len = strlen(path); - char* work = (char*)malloc(len + 1); - if (work == NULL) return NULL; - memcpy(work, path, len + 1); - - for (;;) { - char* slash = strrchr(work, '/'); - if (slash == NULL || slash == work) { - free(work); - return NULL; - } - size_t suffix_off = (size_t)(slash - work); // includes leading '/' - *slash = '\0'; - - canon = realpath(work, NULL); - if (canon != NULL) { - size_t parent_len = strlen(canon); - size_t suffix_len = len - suffix_off; - char* out = (char*)malloc(parent_len + suffix_len + 1); - if (out != NULL) { - memcpy(out, canon, parent_len); - memcpy(out + parent_len, path + suffix_off, suffix_len); - out[parent_len + suffix_len] = '\0'; - } - free(canon); - free(work); - return out; - } - // continue walking up - } -} - -static int starts_with_mount(const char* abs) -{ - if (abs == NULL || g_mount_point == NULL) return 0; - size_t mp_len = strlen(g_mount_point); - if (strncmp(abs, g_mount_point, mp_len) != 0) return 0; - return abs[mp_len] == '\0' || abs[mp_len] == '/'; -} - -// Skip canonicalization for paths that obviously cannot reach the mount, and -// avoid burning a realpath() syscall per stat for system libs / Wine internals. -static int looks_like_system_path(const char* abs) -{ - if (abs == NULL) return 1; - static const char* const skip[] = { - "/proc/", "/sys/", "/dev/", "/run/", "/tmp/", "/usr/", - "/lib/", "/lib64/", "/etc/", "/var/", "/opt/", "/bin/", - "/sbin/", "/boot/", - }; - for (size_t i = 0; i < sizeof(skip) / sizeof(skip[0]); ++i) { - size_t n = strlen(skip[i]); - if (strncmp(abs, skip[i], n) == 0) return 1; - } - return 0; -} - -static char* absolute_path_from_dirfd(int dirfd, const char* path) -{ - if (path == NULL) return NULL; - - char* wine = wine_dos_path_to_unix_dup(path); - if (wine != NULL) return wine; - - if (path[0] == '/' || path[0] == '\\') return normalized_slashes_dup(path); - - char base[4096]; - if (dirfd == AT_FDCWD) { - if (getcwd(base, sizeof(base)) == NULL) return NULL; - } else { - char proc[64]; - snprintf(proc, sizeof(proc), "/proc/self/fd/%d", dirfd); - ssize_t n = readlink(proc, base, sizeof(base) - 1); - if (n < 0) return NULL; - base[n] = '\0'; - } - - size_t len = strlen(base) + 1 + strlen(path) + 1; - char* out = (char*)malloc(len); - if (out == NULL) return NULL; - snprintf(out, len, "%s/%s", base, path); - char* trimmed = normalized_slashes_dup(out); - free(out); - return trimmed; -} - -static int rel_from_mount_at(int dirfd, const char* path, char** rel) -{ - if (path == NULL) return 0; - ensure_loaded(); - if (!g_loaded || g_mount_point == NULL) return 0; - - char* clean = absolute_path_from_dirfd(dirfd, path); - if (clean == NULL) { - sample_path_mismatch("normalize-failed", dirfd, path, NULL, NULL); - return 0; - } - - // Fast path: clean already under mount. - const char* abs = NULL; - char* canon = NULL; - - if (starts_with_mount(clean)) { - abs = clean; - } else if (!looks_like_system_path(clean)) { - __sync_fetch_and_add(&g_canon_attempts, 1); - canon = canonicalize_existing_prefix_dup(clean); - if (canon == NULL) { - __sync_fetch_and_add(&g_canon_failed, 1); - sample_path_mismatch("canon-failed", dirfd, path, clean, NULL); - free(clean); - return 0; - } - if (starts_with_mount(canon)) { - __sync_fetch_and_add(&g_canon_mount_hits, 1); - abs = canon; - } else { - __sync_fetch_and_add(&g_canon_outside, 1); - sample_path_mismatch("outside-mount", dirfd, path, clean, canon); - free(clean); - free(canon); - return 0; - } - } else { - // System paths flood every process; count, do not sample. - __sync_fetch_and_add(&g_path_mismatches, 1); - free(clean); - return 0; - } - - size_t mp_len = strlen(g_mount_point); - if (abs[mp_len] == '\0') { - free(clean); - free(canon); - *rel = strdup(""); - return *rel != NULL; - } - - char* out = strdup(abs + mp_len + 1); - free(clean); - free(canon); - if (out == NULL) return 0; - lowercase_vfs_path(out); - *rel = out; - return 1; -} - -static Entry* resolve_entry_at(int dirfd, const char* path) -{ - char* rel = NULL; - if (!rel_from_mount_at(dirfd, path, &rel)) return NULL; - Entry* e = find_entry(rel); - free(rel); - if (e == NULL) { - __sync_fetch_and_add(&g_resolve_misses, 1); - return NULL; - } - __sync_fetch_and_add(&g_resolve_hits, 1); - return e; -} - -static Entry* resolve_entry(const char* path) -{ - return resolve_entry_at(AT_FDCWD, path); -} - -static char* resolved_real_path(const Entry* e) -{ - if (e == NULL || e->directory || e->real_path == NULL) return NULL; - if (!e->backing || g_data_dir == NULL) return strdup(e->real_path); - - size_t len = strlen(g_data_dir) + 1 + strlen(e->real_path) + 1; - char* out = (char*)malloc(len); - if (out == NULL) return NULL; - snprintf(out, len, "%s/%s", g_data_dir, e->real_path); - return out; -} - -static int read_only_flags(int flags) -{ - return (flags & O_ACCMODE) == O_RDONLY && - (flags & (O_CREAT | O_TRUNC | O_APPEND)) == 0; -} - -static void fill_synthetic_stat(struct stat* st, const Entry* e) -{ - memset(st, 0, sizeof(*st)); - st->st_uid = getuid(); - st->st_gid = getgid(); - st->st_nlink = e->directory ? 2 : 1; - st->st_mode = e->directory ? (S_IFDIR | 0755) : (S_IFREG | 0644); - st->st_size = (off_t)e->size; - st->st_mtim.tv_sec = e->mtime_ns / 1000000000LL; - st->st_mtim.tv_nsec = e->mtime_ns % 1000000000LL; - st->st_atim = st->st_mtim; - st->st_ctim = st->st_mtim; -} - -static void fill_synthetic_statx(struct statx* stx, const Entry* e) -{ - memset(stx, 0, sizeof(*stx)); - stx->stx_mask = STATX_TYPE | STATX_MODE | STATX_NLINK | STATX_UID | - STATX_GID | STATX_SIZE | STATX_MTIME | STATX_ATIME | - STATX_CTIME; - stx->stx_uid = getuid(); - stx->stx_gid = getgid(); - stx->stx_nlink = e->directory ? 2 : 1; - stx->stx_mode = e->directory ? (S_IFDIR | 0755) : (S_IFREG | 0644); - stx->stx_size = e->size; - stx->stx_mtime.tv_sec = e->mtime_ns / 1000000000LL; - stx->stx_mtime.tv_nsec = e->mtime_ns % 1000000000LL; - stx->stx_atime = stx->stx_mtime; - stx->stx_ctime = stx->stx_mtime; -} - -// ---- Directory listing path --------------------------------------------- - -// Last component of a virtual key. "a/b/c" -> "c"; root key "" -> "". -static const char* basename_of(const char* key) -{ - const char* slash = strrchr(key, '/'); - return slash != NULL ? slash + 1 : key; -} - -static FlDir* fl_dir_alloc(size_t entry_idx) -{ - FlDir* dir = (FlDir*)calloc(1, sizeof(FlDir)); - if (dir == NULL) return NULL; - dir->magic = FL_DIR_MAGIC; - dir->entry_idx = entry_idx; - dir->real_fd = -1; - return dir; -} - -static int is_fl_dir(const void* p) -{ - if (p == NULL) return 0; - uint64_t magic; - memcpy(&magic, p, sizeof(magic)); - return magic == FL_DIR_MAGIC; -} - -static void fd_map_register(int fd, FlDir* dir) -{ - if (fd < 0 || dir == NULL) return; - pthread_mutex_lock(&g_fd_map_mutex); - if (g_fd_map_count + 1 >= (g_fd_map_cap >> 1)) { - size_t new_cap = g_fd_map_cap == 0 ? 32 : g_fd_map_cap * 2; - FdSlot* new_map = (FdSlot*)calloc(new_cap, sizeof(FdSlot)); - if (new_map == NULL) { - pthread_mutex_unlock(&g_fd_map_mutex); - return; - } - for (size_t i = 0; i < new_cap; ++i) new_map[i].fd = -1; - for (size_t i = 0; i < g_fd_map_cap; ++i) { - if (g_fd_map[i].fd < 0) continue; - size_t h = ((size_t)g_fd_map[i].fd * 2654435761u) & (new_cap - 1); - while (new_map[h].fd >= 0) h = (h + 1) & (new_cap - 1); - new_map[h] = g_fd_map[i]; - } - free(g_fd_map); - g_fd_map = new_map; - g_fd_map_cap = new_cap; - } - size_t h = ((size_t)fd * 2654435761u) & (g_fd_map_cap - 1); - while (g_fd_map[h].fd >= 0 && g_fd_map[h].fd != fd) { - h = (h + 1) & (g_fd_map_cap - 1); - } - if (g_fd_map[h].fd < 0) ++g_fd_map_count; - g_fd_map[h].fd = fd; - g_fd_map[h].dir = dir; - pthread_mutex_unlock(&g_fd_map_mutex); -} - -static FlDir* fd_map_lookup(int fd) -{ - if (fd < 0 || g_fd_map_cap == 0) return NULL; - pthread_mutex_lock(&g_fd_map_mutex); - size_t h = ((size_t)fd * 2654435761u) & (g_fd_map_cap - 1); - FlDir* found = NULL; - for (size_t probes = 0; probes < g_fd_map_cap; ++probes) { - if (g_fd_map[h].fd < 0) break; - if (g_fd_map[h].fd == fd) { found = g_fd_map[h].dir; break; } - h = (h + 1) & (g_fd_map_cap - 1); - } - pthread_mutex_unlock(&g_fd_map_mutex); - return found; -} - -static FlDir* fd_map_take(int fd) -{ - if (fd < 0 || g_fd_map_cap == 0) return NULL; - pthread_mutex_lock(&g_fd_map_mutex); - size_t h = ((size_t)fd * 2654435761u) & (g_fd_map_cap - 1); - FlDir* found = NULL; - for (size_t probes = 0; probes < g_fd_map_cap; ++probes) { - if (g_fd_map[h].fd < 0) break; - if (g_fd_map[h].fd == fd) { - found = g_fd_map[h].dir; - g_fd_map[h].fd = -1; - g_fd_map[h].dir = NULL; - --g_fd_map_count; - // Re-insert any clustered entries to repair probe chain. - size_t j = (h + 1) & (g_fd_map_cap - 1); - while (g_fd_map[j].fd >= 0) { - FdSlot moved = g_fd_map[j]; - g_fd_map[j].fd = -1; - g_fd_map[j].dir = NULL; - --g_fd_map_count; - size_t k = ((size_t)moved.fd * 2654435761u) & (g_fd_map_cap - 1); - while (g_fd_map[k].fd >= 0) k = (k + 1) & (g_fd_map_cap - 1); - g_fd_map[k] = moved; - ++g_fd_map_count; - j = (j + 1) & (g_fd_map_cap - 1); - } - break; - } - h = (h + 1) & (g_fd_map_cap - 1); - } - pthread_mutex_unlock(&g_fd_map_mutex); - return found; -} - -static FlDir* try_open_synthetic_dir(const char* path) -{ - ensure_loaded(); - if (!g_loaded) return NULL; - Entry* e = resolve_entry(path); - if (e == NULL || !e->directory) return NULL; - size_t idx = (size_t)(e - g_entries); - return fl_dir_alloc(idx); -} - -static FlDir* try_open_synthetic_dir_at(int dirfd, const char* path) -{ - ensure_loaded(); - if (!g_loaded) return NULL; - Entry* e = resolve_entry_at(dirfd, path); - if (e == NULL || !e->directory) return NULL; - size_t idx = (size_t)(e - g_entries); - return fl_dir_alloc(idx); -} - -// /dev/null fd to back DIR* when callers ask for dirfd(). -static int open_devnull_fd(void) -{ - int (*real_open)(const char*, int, ...) = - (int (*)(const char*, int, ...))next_symbol("open"); - if (real_open == NULL) return -1; - g_inside = 1; - int fd = real_open("/dev/null", O_RDONLY | O_CLOEXEC, 0); - g_inside = 0; - return fd; -} - -static unsigned char dirent_type_for(const Entry* child) -{ - return child->directory ? DT_DIR : DT_REG; -} - -// Returns 1 if a record was filled, 0 when we're past the end. -static int fl_dir_next_dirent(FlDir* dir, struct dirent* out) -{ - if (dir == NULL) return 0; - Entry* parent = &g_entries[dir->entry_idx]; - size_t total = (size_t)parent->child_count; - // Synthetic '.' at pos 0, '..' at pos 1, then children. - while (dir->pos < 2 + total) { - size_t p = dir->pos++; - memset(out, 0, sizeof(*out)); - out->d_off = (off_t)dir->pos; - out->d_reclen = sizeof(struct dirent); - if (p == 0) { - out->d_ino = 1; - out->d_type = DT_DIR; - strcpy(out->d_name, "."); - return 1; - } - if (p == 1) { - out->d_ino = 1; - out->d_type = DT_DIR; - strcpy(out->d_name, ".."); - return 1; - } - uint32_t cidx = g_children[parent->child_start + (p - 2)]; - Entry* child = &g_entries[cidx]; - const char* name = basename_of(child->key); - size_t nlen = strlen(name); - if (nlen >= sizeof(out->d_name)) continue; // skip oversize - out->d_ino = (ino_t)(cidx + 2); - out->d_type = dirent_type_for(child); - memcpy(out->d_name, name, nlen + 1); - return 1; - } - return 0; -} - -static int fl_dir_next_dirent64(FlDir* dir, struct dirent64* out) -{ - if (dir == NULL) return 0; - Entry* parent = &g_entries[dir->entry_idx]; - size_t total = (size_t)parent->child_count; - while (dir->pos < 2 + total) { - size_t p = dir->pos++; - memset(out, 0, sizeof(*out)); - out->d_off = (off_t)dir->pos; - out->d_reclen = sizeof(struct dirent64); - if (p == 0) { - out->d_ino = 1; - out->d_type = DT_DIR; - strcpy(out->d_name, "."); - return 1; - } - if (p == 1) { - out->d_ino = 1; - out->d_type = DT_DIR; - strcpy(out->d_name, ".."); - return 1; - } - uint32_t cidx = g_children[parent->child_start + (p - 2)]; - Entry* child = &g_entries[cidx]; - const char* name = basename_of(child->key); - size_t nlen = strlen(name); - if (nlen >= sizeof(out->d_name)) continue; - out->d_ino = (ino64_t)(cidx + 2); - out->d_type = dirent_type_for(child); - memcpy(out->d_name, name, nlen + 1); - return 1; - } - return 0; -} - -// Pack getdents64-style records (struct linux_dirent64). The kernel layout -// matches glibc struct dirent64. Returns bytes written. -static long fl_dir_fill_getdents64(FlDir* dir, void* buf, size_t buf_len) -{ - if (dir == NULL) return 0; - Entry* parent = &g_entries[dir->entry_idx]; - size_t total = (size_t)parent->child_count; - - size_t off = 0; - char* out = (char*)buf; - - while (dir->pos < 2 + total) { - size_t p = dir->pos; - const char* name; - char dot[] = "."; - char dotdot[] = ".."; - Entry* child = NULL; - uint64_t ino = 1; - unsigned char dtype = DT_DIR; - - if (p == 0) { - name = dot; - } else if (p == 1) { - name = dotdot; - } else { - uint32_t cidx = g_children[parent->child_start + (p - 2)]; - child = &g_entries[cidx]; - name = basename_of(child->key); - ino = (uint64_t)(cidx + 2); - dtype = dirent_type_for(child); - } - - size_t nlen = strlen(name); - // struct linux_dirent64 layout: u64 ino, s64 off, u16 reclen, u8 type, char[] name (NUL). - // Aligned to 8. - size_t reclen = 19 + nlen + 1; - reclen = (reclen + 7u) & ~(size_t)7u; - if (off + reclen > buf_len) { - if (off == 0) { - errno = EINVAL; - return -1; - } - break; - } - char* rec = out + off; - memset(rec, 0, reclen); - memcpy(rec, &ino, 8); - int64_t doff = (int64_t)(p + 1); - memcpy(rec + 8, &doff, 8); - uint16_t rl = (uint16_t)reclen; - memcpy(rec + 16, &rl, 2); - rec[18] = (char)dtype; - memcpy(rec + 19, name, nlen + 1); - off += reclen; - ++dir->pos; - } - return (long)off; -} - -// Synthesize a /dev/null-backed fd for an already-resolved directory entry. -// Returns the fd, or -1 on failure. The fd is registered in g_fd_map so -// getdents64 syscalls and fdopendir() can find the FlDir state. -static int synthesize_dir_fd_for(const Entry* e) -{ - if (e == NULL || !e->directory) return -1; - FlDir* dir = fl_dir_alloc((size_t)(e - g_entries)); - if (dir == NULL) return -1; - int fd = open_devnull_fd(); - if (fd < 0) { - free(dir); - return -1; - } - dir->real_fd = fd; - fd_map_register(fd, dir); - __sync_fetch_and_add(&g_opendir_hits, 1); - return fd; -} - -DIR* opendir(const char* name) -{ - __sync_fetch_and_add(&g_opendir_calls, 1); - if (g_dirs_enabled && !g_inside && name != NULL) { - ensure_loaded(); - if (g_loaded) { - Entry* e = resolve_entry(name); - if (e != NULL && e->directory) { - // Allocate FlDir + register backing fd, then return the FlDir cast as - // DIR*. dirfd() will then return the registered /dev/null fd. - FlDir* dir = fl_dir_alloc((size_t)(e - g_entries)); - if (dir != NULL) { - dir->real_fd = open_devnull_fd(); - if (dir->real_fd >= 0) fd_map_register(dir->real_fd, dir); - __sync_fetch_and_add(&g_opendir_hits, 1); - return (DIR*)dir; - } - } - } - } - DIR* (*real_opendir)(const char*) = - (DIR* (*)(const char*))next_symbol("opendir"); - return real_opendir(name); -} - -DIR* fdopendir(int fd) -{ - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - __sync_fetch_and_add(&g_opendir_hits, 1); - return (DIR*)dir; - } - DIR* (*real_fdopendir)(int) = (DIR* (*)(int))next_symbol("fdopendir"); - return real_fdopendir(fd); -} - -struct dirent* readdir(DIR* dir) -{ - __sync_fetch_and_add(&g_readdir_calls, 1); - if (is_fl_dir(dir)) { - FlDir* fl = (FlDir*)dir; - if (fl_dir_next_dirent(fl, &fl->dirent_buf)) { - __sync_fetch_and_add(&g_readdir_hits, 1); - return &fl->dirent_buf; - } - errno = 0; - return NULL; - } - struct dirent* (*real_readdir)(DIR*) = - (struct dirent* (*)(DIR*))next_symbol("readdir"); - return real_readdir(dir); -} - -struct dirent64* readdir64(DIR* dir) -{ - __sync_fetch_and_add(&g_readdir_calls, 1); - if (is_fl_dir(dir)) { - FlDir* fl = (FlDir*)dir; - if (fl_dir_next_dirent64(fl, &fl->dirent64_buf)) { - __sync_fetch_and_add(&g_readdir_hits, 1); - return &fl->dirent64_buf; - } - errno = 0; - return NULL; - } - struct dirent64* (*real_readdir64)(DIR*) = - (struct dirent64* (*)(DIR*))next_symbol("readdir64"); - if (real_readdir64 != NULL) return real_readdir64(dir); - return (struct dirent64*)readdir(dir); -} - -int closedir(DIR* dir) -{ - if (is_fl_dir(dir)) { - FlDir* fl = (FlDir*)dir; - int real_fd = fl->real_fd; - if (real_fd >= 0) fd_map_take(real_fd); - free(fl); - if (real_fd >= 0) { - int (*real_close)(int) = (int (*)(int))next_symbol("close"); - return real_close(real_fd); - } - return 0; - } - int (*real_closedir)(DIR*) = (int (*)(DIR*))next_symbol("closedir"); - return real_closedir(dir); -} - -int dirfd(DIR* dir) -{ - if (is_fl_dir(dir)) return ((FlDir*)dir)->real_fd; - int (*real_dirfd)(DIR*) = (int (*)(DIR*))next_symbol("dirfd"); - return real_dirfd(dir); -} - -void rewinddir(DIR* dir) -{ - if (is_fl_dir(dir)) { - ((FlDir*)dir)->pos = 0; - return; - } - void (*real_rewinddir)(DIR*) = (void (*)(DIR*))next_symbol("rewinddir"); - real_rewinddir(dir); -} - -long telldir(DIR* dir) -{ - if (is_fl_dir(dir)) return (long)((FlDir*)dir)->pos; - long (*real_telldir)(DIR*) = (long (*)(DIR*))next_symbol("telldir"); - return real_telldir(dir); -} - -void seekdir(DIR* dir, long loc) -{ - if (is_fl_dir(dir)) { - ((FlDir*)dir)->pos = (size_t)(loc < 0 ? 0 : loc); - return; - } - void (*real_seekdir)(DIR*, long) = - (void (*)(DIR*, long))next_symbol("seekdir"); - real_seekdir(dir, loc); -} - -int close(int fd) -{ - int (*real_close)(int) = (int (*)(int))next_symbol("close"); - FlDir* dir = fd_map_take(fd); - if (dir != NULL) { - free(dir); - } - return real_close(fd); -} - -// fstat / fstat64: callers (Wine NTDLL especially) frequently fstat a freshly -// opened directory fd to verify it is a directory before issuing getdents64. -// /dev/null would report S_IFCHR and fail that check, so for any fd we -// synthesised we substitute a synthetic directory stat from the index. -int fstat(int fd, struct stat* st) -{ - if (st != NULL) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - fill_synthetic_stat(st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - int (*real_fstat)(int, struct stat*) = - (int (*)(int, struct stat*))next_symbol("fstat"); - return real_fstat(fd, st); -} - -int fstat64(int fd, struct stat64* st) -{ - if (st != NULL) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - // struct stat64 layout matches struct stat on x86_64 glibc. - fill_synthetic_stat((struct stat*)st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - int (*real_fstat64)(int, struct stat64*) = - (int (*)(int, struct stat64*))next_symbol("fstat64"); - if (real_fstat64 == NULL) return fstat(fd, (struct stat*)st); - return real_fstat64(fd, st); -} - -// Old glibc compat (still used by some packaged binaries). -int __fxstat(int ver, int fd, struct stat* st) -{ - if (st != NULL) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - fill_synthetic_stat(st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - int (*real)(int, int, struct stat*) = - (int (*)(int, int, struct stat*))next_symbol("__fxstat"); - if (real == NULL) return fstat(fd, st); - return real(ver, fd, st); -} - -int __fxstat64(int ver, int fd, struct stat64* st) -{ - if (st != NULL) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - fill_synthetic_stat((struct stat*)st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - int (*real)(int, int, struct stat64*) = - (int (*)(int, int, struct stat64*))next_symbol("__fxstat64"); - if (real == NULL) return fstat64(fd, st); - return real(ver, fd, st); -} - -// fcntl(F_GETFL) on the synthetic /dev/null fd reports O_RDONLY which matches -// what NTDLL wants to see for an O_DIRECTORY|O_RDONLY open, so leave that -// alone. lseek(fd, 0, SEEK_SET) is the rewind probe NTDLL emits before -// re-reading entries — translate it to a pos reset on our FlDir. -off_t lseek(int fd, off_t offset, int whence) -{ - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - if (whence == SEEK_SET) { - dir->pos = (size_t)(offset < 0 ? 0 : offset); - return offset; - } - if (whence == SEEK_CUR) { - // Allow telldir-style queries. - return (off_t)dir->pos; - } - } - off_t (*real_lseek)(int, off_t, int) = - (off_t (*)(int, off_t, int))next_symbol("lseek"); - return real_lseek(fd, offset, whence); -} - -off_t lseek64(int fd, off_t offset, int whence) -{ - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - if (whence == SEEK_SET) { - dir->pos = (size_t)(offset < 0 ? 0 : offset); - return offset; - } - if (whence == SEEK_CUR) return (off_t)dir->pos; - } - off_t (*real_lseek64)(int, off_t, int) = - (off_t (*)(int, off_t, int))next_symbol("lseek64"); - if (real_lseek64 == NULL) return lseek(fd, offset, whence); - return real_lseek64(fd, offset, whence); -} - -__attribute__((constructor)) -static void preload_attach(void) -{ - g_stats = flag_enabled("FLUORINE_VFS_PRELOAD_STATS"); -} - -__attribute__((destructor)) -static void preload_detach(void) -{ - if (!g_stats) return; - if (!g_debug && g_open_calls == 0 && g_stat_calls == 0 && g_statx_calls == 0 && - g_access_calls == 0 && g_syscall_calls == 0 && g_resolve_hits == 0 && - g_resolve_misses == 0) { - return; - } - fprintf(stderr, - "[fluorine-vfs-preload] pid=%ld loaded=%d failed=%d entries=%zu " - "open_calls=%llu stat_calls=%llu statx_calls=%llu access_calls=%llu " - "syscall_calls=%llu " - "path_mismatch=%llu " - "canon_attempts=%llu canon_mount_hits=%llu canon_outside=%llu canon_failed=%llu " - "opendir_calls=%llu opendir_hits=%llu " - "readdir_calls=%llu readdir_hits=%llu " - "getdents_calls=%llu getdents_hits=%llu " - "getdents_libc_calls=%llu getdents_libc_hits=%llu " - "resolve_hit=%llu resolve_miss=%llu open_hit=%llu open_fallback=%llu " - "stat_hit=%llu access_hit=%llu mount='%s'\n", - (long)getpid(), g_loaded, g_load_failed, g_entry_count, - (unsigned long long)g_open_calls, - (unsigned long long)g_stat_calls, - (unsigned long long)g_statx_calls, - (unsigned long long)g_access_calls, - (unsigned long long)g_syscall_calls, - (unsigned long long)g_path_mismatches, - (unsigned long long)g_canon_attempts, - (unsigned long long)g_canon_mount_hits, - (unsigned long long)g_canon_outside, - (unsigned long long)g_canon_failed, - (unsigned long long)g_opendir_calls, - (unsigned long long)g_opendir_hits, - (unsigned long long)g_readdir_calls, - (unsigned long long)g_readdir_hits, - (unsigned long long)g_getdents_calls, - (unsigned long long)g_getdents_hits, - (unsigned long long)g_getdents_libc_calls, - (unsigned long long)g_getdents_libc_hits, - (unsigned long long)g_resolve_hits, - (unsigned long long)g_resolve_misses, - (unsigned long long)g_open_hits, - (unsigned long long)g_open_fallbacks, - (unsigned long long)g_stat_hits, - (unsigned long long)g_access_hits, - g_mount_point ? g_mount_point : ""); -} - -int open(const char* pathname, int flags, ...) -{ - mode_t mode = 0; - if (flags & O_CREAT) { - va_list ap; - va_start(ap, flags); - mode = (mode_t)va_arg(ap, int); - va_end(ap); - } - - int (*real_open)(const char*, int, ...) = - (int (*)(const char*, int, ...))next_symbol("open"); - __sync_fetch_and_add(&g_open_calls, 1); - if (g_inside || !read_only_flags(flags)) return real_open(pathname, flags, mode); - - Entry* e = resolve_entry(pathname); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path == NULL) return real_open(pathname, flags, mode); - - g_inside = 1; - int fd = real_open(real_path, flags, mode); - if (fd < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - fd = real_open(pathname, flags, mode); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return fd; -} - -int open64(const char* pathname, int flags, ...) -{ - mode_t mode = 0; - if (flags & O_CREAT) { - va_list ap; - va_start(ap, flags); - mode = (mode_t)va_arg(ap, int); - va_end(ap); - } - int (*real_open64)(const char*, int, ...) = - (int (*)(const char*, int, ...))next_symbol("open64"); - if (real_open64 == NULL) real_open64 = (int (*)(const char*, int, ...))next_symbol("open"); - __sync_fetch_and_add(&g_open_calls, 1); - if (g_inside || !read_only_flags(flags)) return real_open64(pathname, flags, mode); - - Entry* e = resolve_entry(pathname); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path == NULL) return real_open64(pathname, flags, mode); - - g_inside = 1; - int fd = real_open64(real_path, flags, mode); - if (fd < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - fd = real_open64(pathname, flags, mode); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return fd; -} - -int openat(int dirfd, const char* pathname, int flags, ...) -{ - mode_t mode = 0; - if (flags & O_CREAT) { - va_list ap; - va_start(ap, flags); - mode = (mode_t)va_arg(ap, int); - va_end(ap); - } - int (*real_openat)(int, const char*, int, ...) = - (int (*)(int, const char*, int, ...))next_symbol("openat"); - __sync_fetch_and_add(&g_open_calls, 1); - if (g_inside || pathname == NULL || !read_only_flags(flags)) { - return real_openat(dirfd, pathname, flags, mode); - } - - Entry* e = resolve_entry_at(dirfd, pathname); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path == NULL) return real_openat(dirfd, pathname, flags, mode); - - g_inside = 1; - int fd = real_openat(AT_FDCWD, real_path, flags, mode); - if (fd < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - fd = real_openat(dirfd, pathname, flags, mode); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return fd; -} - -int openat64(int dirfd, const char* pathname, int flags, ...) -{ - mode_t mode = 0; - if (flags & O_CREAT) { - va_list ap; - va_start(ap, flags); - mode = (mode_t)va_arg(ap, int); - va_end(ap); - } - int (*real_openat64)(int, const char*, int, ...) = - (int (*)(int, const char*, int, ...))next_symbol("openat64"); - if (real_openat64 == NULL) real_openat64 = (int (*)(int, const char*, int, ...))next_symbol("openat"); - __sync_fetch_and_add(&g_open_calls, 1); - if (g_inside || pathname == NULL || !read_only_flags(flags)) { - return real_openat64(dirfd, pathname, flags, mode); - } - - Entry* e = resolve_entry_at(dirfd, pathname); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path == NULL) return real_openat64(dirfd, pathname, flags, mode); - - g_inside = 1; - int fd = real_openat64(AT_FDCWD, real_path, flags, mode); - if (fd < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - fd = real_openat64(dirfd, pathname, flags, mode); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return fd; -} - -static int mapped_stat(const char* path, struct stat* st, - int (*real_fn)(const char*, struct stat*)) -{ - if (g_inside) return real_fn(path, st); - __sync_fetch_and_add(&g_stat_calls, 1); - Entry* e = resolve_entry(path); - if (e == NULL) return real_fn(path, st); - char* real_path = resolved_real_path(e); - - g_inside = 1; - int rc = 0; - if (e->directory || real_path == NULL) { - fill_synthetic_stat(st, e); - } else { - rc = real_fn(real_path, st); - if (rc != 0) { - fill_synthetic_stat(st, e); - rc = 0; - } - } - if (rc == 0) __sync_fetch_and_add(&g_stat_hits, 1); - g_inside = 0; - free(real_path); - return rc; -} - -int stat(const char* path, struct stat* st) -{ - return mapped_stat(path, st, (int (*)(const char*, struct stat*))next_symbol("stat")); -} - -int lstat(const char* path, struct stat* st) -{ - return mapped_stat(path, st, (int (*)(const char*, struct stat*))next_symbol("lstat")); -} - -int fstatat(int dirfd, const char* path, struct stat* st, int flags) -{ - int (*real_fstatat)(int, const char*, struct stat*, int) = - (int (*)(int, const char*, struct stat*, int))next_symbol("fstatat"); - __sync_fetch_and_add(&g_stat_calls, 1); - if (st != NULL && (flags & AT_EMPTY_PATH) && - (path == NULL || path[0] == '\0')) { - FlDir* dir = fd_map_lookup(dirfd); - if (dir != NULL) { - fill_synthetic_stat(st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - if (g_inside || path == NULL) return real_fstatat(dirfd, path, st, flags); - Entry* e = resolve_entry_at(dirfd, path); - if (e == NULL) return real_fstatat(dirfd, path, st, flags); - char* real_path = resolved_real_path(e); - - g_inside = 1; - int rc = 0; - if (e->directory || real_path == NULL) { - fill_synthetic_stat(st, e); - } else { - rc = real_fstatat(AT_FDCWD, real_path, st, flags); - if (rc != 0) { - fill_synthetic_stat(st, e); - rc = 0; - } - } - if (rc == 0) __sync_fetch_and_add(&g_stat_hits, 1); - g_inside = 0; - free(real_path); - return rc; -} - -int access(const char* path, int mode) -{ - int (*real_access)(const char*, int) = (int (*)(const char*, int))next_symbol("access"); - __sync_fetch_and_add(&g_access_calls, 1); - if (g_inside) return real_access(path, mode); - Entry* e = resolve_entry(path); - if (e == NULL) return real_access(path, mode); - char* real_path = resolved_real_path(e); - int rc = 0; - if (mode == F_OK || e->directory || real_path == NULL) { - rc = 0; - } else { - rc = real_access(real_path, mode); - } - if (rc == 0) __sync_fetch_and_add(&g_access_hits, 1); - free(real_path); - return rc; -} - -int faccessat(int dirfd, const char* path, int mode, int flags) -{ - int (*real_faccessat)(int, const char*, int, int) = - (int (*)(int, const char*, int, int))next_symbol("faccessat"); - __sync_fetch_and_add(&g_access_calls, 1); - if (g_inside || path == NULL) return real_faccessat(dirfd, path, mode, flags); - Entry* e = resolve_entry_at(dirfd, path); - if (e == NULL) return real_faccessat(dirfd, path, mode, flags); - char* real_path = resolved_real_path(e); - int rc = 0; - if (mode == F_OK || e->directory || real_path == NULL) { - rc = 0; - } else { - rc = real_faccessat(AT_FDCWD, real_path, mode, flags); - } - if (rc == 0) __sync_fetch_and_add(&g_access_hits, 1); - free(real_path); - return rc; -} - -int statx(int dirfd, const char* path, int flags, unsigned int mask, struct statx* stx) -{ - int (*real_statx)(int, const char*, int, unsigned int, struct statx*) = - (int (*)(int, const char*, int, unsigned int, struct statx*))next_symbol("statx"); - __sync_fetch_and_add(&g_statx_calls, 1); - if (g_inside || path == NULL) { - return real_statx ? real_statx(dirfd, path, flags, mask, stx) - : (int)syscall(SYS_statx, dirfd, path, flags, mask, stx); - } - Entry* e = resolve_entry_at(dirfd, path); - if (e == NULL) { - return real_statx ? real_statx(dirfd, path, flags, mask, stx) - : (int)syscall(SYS_statx, dirfd, path, flags, mask, stx); - } - char* real_path = resolved_real_path(e); - - g_inside = 1; - int rc = 0; - if (e->directory || real_path == NULL) { - fill_synthetic_statx(stx, e); - } else { - rc = real_statx ? real_statx(AT_FDCWD, real_path, flags, mask, stx) - : (int)syscall(SYS_statx, AT_FDCWD, real_path, flags, mask, stx); - if (rc != 0) { - fill_synthetic_statx(stx, e); - rc = 0; - } - } - if (rc == 0) __sync_fetch_and_add(&g_stat_hits, 1); - g_inside = 0; - free(real_path); - return rc; -} - -// Wine 9+ / glibc 2.30+ may resolve getdents64() as a libc symbol rather than -// emitting syscall(SYS_getdents64, ...). Hook the symbol directly so synthetic -// dir fds answer NTDLL's directory reads. Mirrors the SYS_getdents64 branch in -// syscall(). -ssize_t getdents64(int fd, void* buf, size_t count) -{ - __sync_fetch_and_add(&g_getdents_libc_calls, 1); - if (!g_inside) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL && buf != NULL) { - long n = fl_dir_fill_getdents64(dir, buf, count); - if (n >= 0) __sync_fetch_and_add(&g_getdents_libc_hits, 1); - return (ssize_t)n; - } - } - ssize_t (*real)(int, void*, size_t) = - (ssize_t (*)(int, void*, size_t))next_symbol("getdents64"); - if (real != NULL) return real(fd, buf, count); - return (ssize_t)syscall(SYS_getdents64, fd, buf, count); -} - -long syscall(long number, ...) -{ - va_list ap; - va_start(ap, number); - long a1 = va_arg(ap, long); - long a2 = va_arg(ap, long); - long a3 = va_arg(ap, long); - long a4 = va_arg(ap, long); - long a5 = va_arg(ap, long); - long a6 = va_arg(ap, long); - va_end(ap); - - long (*real_syscall)(long, ...) = (long (*)(long, ...))next_symbol("syscall"); - __sync_fetch_and_add(&g_syscall_calls, 1); - - if (g_inside) { - return real_syscall(number, a1, a2, a3, a4, a5, a6); - } - -#ifdef SYS_openat - if (number == SYS_openat) { - int dirfd = (int)a1; - const char* path = (const char*)a2; - int flags = (int)a3; - mode_t mode = (mode_t)a4; - if (path != NULL && read_only_flags(flags)) { - Entry* e = resolve_entry_at(dirfd, path); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path != NULL) { - g_inside = 1; - long rc = real_syscall(number, AT_FDCWD, (long)real_path, flags, mode); - if (rc < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - rc = real_syscall(number, a1, a2, a3, a4, a5, a6); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return rc; - } - } - } -#endif - -#ifdef SYS_getdents64 - if (number == SYS_getdents64) { - int fd = (int)a1; - void* buf = (void*)a2; - size_t count = (size_t)a3; - __sync_fetch_and_add(&g_getdents_calls, 1); - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL && buf != NULL) { - long n = fl_dir_fill_getdents64(dir, buf, count); - if (n >= 0) __sync_fetch_and_add(&g_getdents_hits, 1); - return n; - } - } -#endif - -#ifdef SYS_fstat - if (number == SYS_fstat) { - int fd = (int)a1; - struct stat* st = (struct stat*)a2; - if (st != NULL) { - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - fill_synthetic_stat(st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - } -#endif - -#ifdef SYS_close - if (number == SYS_close) { - int fd = (int)a1; - FlDir* dir = fd_map_take(fd); - if (dir != NULL) free(dir); - } -#endif - -#ifdef SYS_lseek - if (number == SYS_lseek) { - int fd = (int)a1; - long offset = a2; - int whence = (int)a3; - FlDir* dir = fd_map_lookup(fd); - if (dir != NULL) { - if (whence == SEEK_SET) { - dir->pos = (size_t)(offset < 0 ? 0 : offset); - return offset; - } - if (whence == SEEK_CUR) return (long)dir->pos; - } - } -#endif - -#ifdef SYS_open - if (number == SYS_open) { - const char* path = (const char*)a1; - int flags = (int)a2; - mode_t mode = (mode_t)a3; - if (path != NULL && read_only_flags(flags)) { - Entry* e = resolve_entry(path); - if (g_dirs_enabled && e != NULL && e->directory) { - int newfd = synthesize_dir_fd_for(e); - if (newfd >= 0) return newfd; - } - char* real_path = resolved_real_path(e); - if (real_path != NULL) { - g_inside = 1; - long rc = real_syscall(number, (long)real_path, flags, mode, a4, a5, a6); - if (rc < 0) { - __sync_fetch_and_add(&g_open_fallbacks, 1); - rc = real_syscall(number, a1, a2, a3, a4, a5, a6); - } else { - __sync_fetch_and_add(&g_open_hits, 1); - } - g_inside = 0; - free(real_path); - return rc; - } - } - } -#endif - -#ifdef SYS_newfstatat - if (number == SYS_newfstatat) { - int dirfd = (int)a1; - const char* path = (const char*)a2; - struct stat* st = (struct stat*)a3; - int flags = (int)a4; - // AT_EMPTY_PATH + empty path = fstat-on-dirfd. Wine NTDLL emits this to - // verify the directory fd before calling getdents64. - if (st != NULL && (flags & AT_EMPTY_PATH) && - (path == NULL || path[0] == '\0')) { - FlDir* dir = fd_map_lookup(dirfd); - if (dir != NULL) { - fill_synthetic_stat(st, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - if (path != NULL && st != NULL) { - Entry* e = resolve_entry_at(dirfd, path); - char* real_path = resolved_real_path(e); - if (e != NULL) { - g_inside = 1; - long rc = 0; - if (e->directory || real_path == NULL) { - fill_synthetic_stat(st, e); - } else { - rc = real_syscall(number, AT_FDCWD, (long)real_path, (long)st, flags, a5, a6); - if (rc != 0) { - fill_synthetic_stat(st, e); - rc = 0; - } - } - if (rc == 0) __sync_fetch_and_add(&g_stat_hits, 1); - g_inside = 0; - free(real_path); - return rc; - } - free(real_path); - } - } -#endif - -#ifdef SYS_statx - if (number == SYS_statx) { - int dirfd = (int)a1; - const char* path = (const char*)a2; - int flags = (int)a3; - unsigned int mask = (unsigned int)a4; - struct statx* stx = (struct statx*)a5; - if (stx != NULL && (flags & AT_EMPTY_PATH) && - (path == NULL || path[0] == '\0')) { - FlDir* dir = fd_map_lookup(dirfd); - if (dir != NULL) { - fill_synthetic_statx(stx, &g_entries[dir->entry_idx]); - __sync_fetch_and_add(&g_stat_hits, 1); - return 0; - } - } - if (path != NULL && stx != NULL) { - Entry* e = resolve_entry_at(dirfd, path); - char* real_path = resolved_real_path(e); - if (e != NULL) { - g_inside = 1; - long rc = 0; - if (e->directory || real_path == NULL) { - fill_synthetic_statx(stx, e); - } else { - rc = real_syscall(number, AT_FDCWD, (long)real_path, flags, mask, (long)stx, a6); - if (rc != 0) { - fill_synthetic_statx(stx, e); - rc = 0; - } - } - if (rc == 0) __sync_fetch_and_add(&g_stat_hits, 1); - g_inside = 0; - free(real_path); - return rc; - } - free(real_path); - } - } -#endif - -#ifdef SYS_access - if (number == SYS_access) { - const char* path = (const char*)a1; - int mode = (int)a2; - if (path != NULL) { - Entry* e = resolve_entry(path); - char* real_path = resolved_real_path(e); - if (e != NULL) { - long rc = 0; - if (mode == F_OK || e->directory || real_path == NULL) { - rc = 0; - } else { - rc = real_syscall(number, (long)real_path, mode, a3, a4, a5, a6); - } - if (rc == 0) __sync_fetch_and_add(&g_access_hits, 1); - free(real_path); - return rc; - } - free(real_path); - } - } -#endif - -#ifdef SYS_faccessat - if (number == SYS_faccessat) { - int dirfd = (int)a1; - const char* path = (const char*)a2; - int mode = (int)a3; - if (path != NULL) { - Entry* e = resolve_entry_at(dirfd, path); - char* real_path = resolved_real_path(e); - if (e != NULL) { - long rc = 0; - if (mode == F_OK || e->directory || real_path == NULL) { - rc = 0; - } else { - rc = real_syscall(number, AT_FDCWD, (long)real_path, mode, a4, a5, a6); - } - if (rc == 0) __sync_fetch_and_add(&g_access_hits, 1); - free(real_path); - return rc; - } - free(real_path); - } - } -#endif - - return real_syscall(number, a1, a2, a3, a4, a5, a6); -} diff --git a/third_party/minhook/AUTHORS.txt b/third_party/minhook/AUTHORS.txt deleted file mode 100644 index ebef1a6..0000000 --- a/third_party/minhook/AUTHORS.txt +++ /dev/null @@ -1,8 +0,0 @@ -Tsuda Kageyu <tsuda.kageyu@gmail.com> - Creator, maintainer - -Michael Maltsev <leahcimmar@gmail.com> - Added "Queue" functions. A lot of bug fixes. - -Andrey Unis <uniskz@gmail.com> - Rewrote the hook engine in plain C. diff --git a/third_party/minhook/LICENSE.txt b/third_party/minhook/LICENSE.txt deleted file mode 100644 index 74dea27..0000000 --- a/third_party/minhook/LICENSE.txt +++ /dev/null @@ -1,81 +0,0 @@ -MinHook - The Minimalistic API Hooking Library for x64/x86 -Copyright (C) 2009-2017 Tsuda Kageyu. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -================================================================================ -Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. -================================================================================ -Hacker Disassembler Engine 32 C -Copyright (c) 2008-2009, Vyacheslav Patkov. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -------------------------------------------------------------------------------- -Hacker Disassembler Engine 64 C -Copyright (c) 2008-2009, Vyacheslav Patkov. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/minhook/include/MinHook.h b/third_party/minhook/include/MinHook.h deleted file mode 100644 index ae39350..0000000 --- a/third_party/minhook/include/MinHook.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) - #error MinHook supports only x86 and x64 systems. -#endif - -#include <windows.h> - -// MinHook Error Codes. -typedef enum MH_STATUS -{ - // Unknown error. Should not be returned. - MH_UNKNOWN = -1, - - // Successful. - MH_OK = 0, - - // MinHook is already initialized. - MH_ERROR_ALREADY_INITIALIZED, - - // MinHook is not initialized yet, or already uninitialized. - MH_ERROR_NOT_INITIALIZED, - - // The hook for the specified target function is already created. - MH_ERROR_ALREADY_CREATED, - - // The hook for the specified target function is not created yet. - MH_ERROR_NOT_CREATED, - - // The hook for the specified target function is already enabled. - MH_ERROR_ENABLED, - - // The hook for the specified target function is not enabled yet, or already - // disabled. - MH_ERROR_DISABLED, - - // The specified pointer is invalid. It points the address of non-allocated - // and/or non-executable region. - MH_ERROR_NOT_EXECUTABLE, - - // The specified target function cannot be hooked. - MH_ERROR_UNSUPPORTED_FUNCTION, - - // Failed to allocate memory. - MH_ERROR_MEMORY_ALLOC, - - // Failed to change the memory protection. - MH_ERROR_MEMORY_PROTECT, - - // The specified module is not loaded. - MH_ERROR_MODULE_NOT_FOUND, - - // The specified function is not found. - MH_ERROR_FUNCTION_NOT_FOUND -} -MH_STATUS; - -// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, -// MH_QueueEnableHook or MH_QueueDisableHook. -#define MH_ALL_HOOKS NULL - -#ifdef __cplusplus -extern "C" { -#endif - - // Initialize the MinHook library. You must call this function EXACTLY ONCE - // at the beginning of your program. - MH_STATUS WINAPI MH_Initialize(VOID); - - // Uninitialize the MinHook library. You must call this function EXACTLY - // ONCE at the end of your program. - MH_STATUS WINAPI MH_Uninitialize(VOID); - - // Creates a hook for the specified target function, in disabled state. - // Parameters: - // pTarget [in] A pointer to the target function, which will be - // overridden by the detour function. - // pDetour [in] A pointer to the detour function, which will override - // the target function. - // ppOriginal [out] A pointer to the trampoline function, which will be - // used to call the original target function. - // This parameter can be NULL. - MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); - - // Creates a hook for the specified API function, in disabled state. - // Parameters: - // pszModule [in] A pointer to the loaded module name which contains the - // target function. - // pszProcName [in] A pointer to the target function name, which will be - // overridden by the detour function. - // pDetour [in] A pointer to the detour function, which will override - // the target function. - // ppOriginal [out] A pointer to the trampoline function, which will be - // used to call the original target function. - // This parameter can be NULL. - MH_STATUS WINAPI MH_CreateHookApi( - LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); - - // Creates a hook for the specified API function, in disabled state. - // Parameters: - // pszModule [in] A pointer to the loaded module name which contains the - // target function. - // pszProcName [in] A pointer to the target function name, which will be - // overridden by the detour function. - // pDetour [in] A pointer to the detour function, which will override - // the target function. - // ppOriginal [out] A pointer to the trampoline function, which will be - // used to call the original target function. - // This parameter can be NULL. - // ppTarget [out] A pointer to the target function, which will be used - // with other functions. - // This parameter can be NULL. - MH_STATUS WINAPI MH_CreateHookApiEx( - LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); - - // Removes an already created hook. - // Parameters: - // pTarget [in] A pointer to the target function. - MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); - - // Enables an already created hook. - // Parameters: - // pTarget [in] A pointer to the target function. - // If this parameter is MH_ALL_HOOKS, all created hooks are - // enabled in one go. - MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); - - // Disables an already created hook. - // Parameters: - // pTarget [in] A pointer to the target function. - // If this parameter is MH_ALL_HOOKS, all created hooks are - // disabled in one go. - MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); - - // Queues to enable an already created hook. - // Parameters: - // pTarget [in] A pointer to the target function. - // If this parameter is MH_ALL_HOOKS, all created hooks are - // queued to be enabled. - MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); - - // Queues to disable an already created hook. - // Parameters: - // pTarget [in] A pointer to the target function. - // If this parameter is MH_ALL_HOOKS, all created hooks are - // queued to be disabled. - MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); - - // Applies all queued changes in one go. - MH_STATUS WINAPI MH_ApplyQueued(VOID); - - // Translates the MH_STATUS to its name as a string. - const char *WINAPI MH_StatusToString(MH_STATUS status); - -#ifdef __cplusplus -} -#endif diff --git a/third_party/minhook/src/buffer.c b/third_party/minhook/src/buffer.c deleted file mode 100644 index 940df65..0000000 --- a/third_party/minhook/src/buffer.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include <windows.h> -#include "buffer.h" - -// Size of each memory block. (= page size of VirtualAlloc) -#define MEMORY_BLOCK_SIZE 0x1000 - -// Max range for seeking a memory block. (= 1024MB) -#define MAX_MEMORY_RANGE 0x40000000 - -// Memory protection flags to check the executable address. -#define PAGE_EXECUTE_FLAGS \ - (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) - -// Memory slot. -typedef struct _MEMORY_SLOT -{ - union - { - struct _MEMORY_SLOT *pNext; - UINT8 buffer[MEMORY_SLOT_SIZE]; - }; -} MEMORY_SLOT, *PMEMORY_SLOT; - -// Memory block info. Placed at the head of each block. -typedef struct _MEMORY_BLOCK -{ - struct _MEMORY_BLOCK *pNext; - PMEMORY_SLOT pFree; // First element of the free slot list. - UINT usedCount; -} MEMORY_BLOCK, *PMEMORY_BLOCK; - -//------------------------------------------------------------------------- -// Global Variables: -//------------------------------------------------------------------------- - -// First element of the memory block list. -static PMEMORY_BLOCK g_pMemoryBlocks; - -//------------------------------------------------------------------------- -VOID InitializeBuffer(VOID) -{ - // Nothing to do for now. -} - -//------------------------------------------------------------------------- -VOID UninitializeBuffer(VOID) -{ - PMEMORY_BLOCK pBlock = g_pMemoryBlocks; - g_pMemoryBlocks = NULL; - - while (pBlock) - { - PMEMORY_BLOCK pNext = pBlock->pNext; - VirtualFree(pBlock, 0, MEM_RELEASE); - pBlock = pNext; - } -} - -//------------------------------------------------------------------------- -#if defined(_M_X64) || defined(__x86_64__) -static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity) -{ - ULONG_PTR tryAddr = (ULONG_PTR)pAddress; - - // Round down to the allocation granularity. - tryAddr -= tryAddr % dwAllocationGranularity; - - // Start from the previous allocation granularity multiply. - tryAddr -= dwAllocationGranularity; - - while (tryAddr >= (ULONG_PTR)pMinAddr) - { - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) - break; - - if (mbi.State == MEM_FREE) - return (LPVOID)tryAddr; - - if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity) - break; - - tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity; - } - - return NULL; -} -#endif - -//------------------------------------------------------------------------- -#if defined(_M_X64) || defined(__x86_64__) -static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity) -{ - ULONG_PTR tryAddr = (ULONG_PTR)pAddress; - - // Round down to the allocation granularity. - tryAddr -= tryAddr % dwAllocationGranularity; - - // Start from the next allocation granularity multiply. - tryAddr += dwAllocationGranularity; - - while (tryAddr <= (ULONG_PTR)pMaxAddr) - { - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) - break; - - if (mbi.State == MEM_FREE) - return (LPVOID)tryAddr; - - tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize; - - // Round up to the next allocation granularity. - tryAddr += dwAllocationGranularity - 1; - tryAddr -= tryAddr % dwAllocationGranularity; - } - - return NULL; -} -#endif - -//------------------------------------------------------------------------- -static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin) -{ - PMEMORY_BLOCK pBlock; -#if defined(_M_X64) || defined(__x86_64__) - ULONG_PTR minAddr; - ULONG_PTR maxAddr; - - SYSTEM_INFO si; - GetSystemInfo(&si); - minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress; - maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress; - - // pOrigin ± 512MB - if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE) - minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE; - - if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE) - maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE; - - // Make room for MEMORY_BLOCK_SIZE bytes. - maxAddr -= MEMORY_BLOCK_SIZE - 1; -#endif - - // Look the registered blocks for a reachable one. - for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext) - { -#if defined(_M_X64) || defined(__x86_64__) - // Ignore the blocks too far. - if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr) - continue; -#endif - // The block has at least one unused slot. - if (pBlock->pFree != NULL) - return pBlock; - } - -#if defined(_M_X64) || defined(__x86_64__) - // Alloc a new block above if not found. - { - LPVOID pAlloc = pOrigin; - while ((ULONG_PTR)pAlloc >= minAddr) - { - pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity); - if (pAlloc == NULL) - break; - - pBlock = (PMEMORY_BLOCK)VirtualAlloc( - pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (pBlock != NULL) - break; - } - } - - // Alloc a new block below if not found. - if (pBlock == NULL) - { - LPVOID pAlloc = pOrigin; - while ((ULONG_PTR)pAlloc <= maxAddr) - { - pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity); - if (pAlloc == NULL) - break; - - pBlock = (PMEMORY_BLOCK)VirtualAlloc( - pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (pBlock != NULL) - break; - } - } -#else - // In x86 mode, a memory block can be placed anywhere. - pBlock = (PMEMORY_BLOCK)VirtualAlloc( - NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); -#endif - - if (pBlock != NULL) - { - // Build a linked list of all the slots. - PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1; - pBlock->pFree = NULL; - pBlock->usedCount = 0; - do - { - pSlot->pNext = pBlock->pFree; - pBlock->pFree = pSlot; - pSlot++; - } while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE); - - pBlock->pNext = g_pMemoryBlocks; - g_pMemoryBlocks = pBlock; - } - - return pBlock; -} - -//------------------------------------------------------------------------- -LPVOID AllocateBuffer(LPVOID pOrigin) -{ - PMEMORY_SLOT pSlot; - PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin); - if (pBlock == NULL) - return NULL; - - // Remove an unused slot from the list. - pSlot = pBlock->pFree; - pBlock->pFree = pSlot->pNext; - pBlock->usedCount++; -#ifdef _DEBUG - // Fill the slot with INT3 for debugging. - memset(pSlot, 0xCC, sizeof(MEMORY_SLOT)); -#endif - return pSlot; -} - -//------------------------------------------------------------------------- -VOID FreeBuffer(LPVOID pBuffer) -{ - PMEMORY_BLOCK pBlock = g_pMemoryBlocks; - PMEMORY_BLOCK pPrev = NULL; - ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE; - - while (pBlock != NULL) - { - if ((ULONG_PTR)pBlock == pTargetBlock) - { - PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer; -#ifdef _DEBUG - // Clear the released slot for debugging. - memset(pSlot, 0x00, sizeof(MEMORY_SLOT)); -#endif - // Restore the released slot to the list. - pSlot->pNext = pBlock->pFree; - pBlock->pFree = pSlot; - pBlock->usedCount--; - - // Free if unused. - if (pBlock->usedCount == 0) - { - if (pPrev) - pPrev->pNext = pBlock->pNext; - else - g_pMemoryBlocks = pBlock->pNext; - - VirtualFree(pBlock, 0, MEM_RELEASE); - } - - break; - } - - pPrev = pBlock; - pBlock = pBlock->pNext; - } -} - -//------------------------------------------------------------------------- -BOOL IsExecutableAddress(LPVOID pAddress) -{ - MEMORY_BASIC_INFORMATION mi; - VirtualQuery(pAddress, &mi, sizeof(mi)); - - return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS)); -} diff --git a/third_party/minhook/src/buffer.h b/third_party/minhook/src/buffer.h deleted file mode 100644 index 204d551..0000000 --- a/third_party/minhook/src/buffer.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -// Size of each memory slot. -#if defined(_M_X64) || defined(__x86_64__) - #define MEMORY_SLOT_SIZE 64 -#else - #define MEMORY_SLOT_SIZE 32 -#endif - -VOID InitializeBuffer(VOID); -VOID UninitializeBuffer(VOID); -LPVOID AllocateBuffer(LPVOID pOrigin); -VOID FreeBuffer(LPVOID pBuffer); -BOOL IsExecutableAddress(LPVOID pAddress); diff --git a/third_party/minhook/src/hde/hde64.c b/third_party/minhook/src/hde/hde64.c deleted file mode 100644 index ce773b1..0000000 --- a/third_party/minhook/src/hde/hde64.c +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Hacker Disassembler Engine 64 C - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - */ - -#if defined(_M_X64) || defined(__x86_64__) - -#include <string.h> -#include "hde64.h" -#include "table64.h" - -unsigned int hde64_disasm(const void *code, hde64s *hs) -{ - uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; - uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; - uint8_t op64 = 0; - - memset(hs, 0, sizeof(hde64s)); - - for (x = 16; x; x--) - switch (c = *p++) { - case 0xf3: - hs->p_rep = c; - pref |= PRE_F3; - break; - case 0xf2: - hs->p_rep = c; - pref |= PRE_F2; - break; - case 0xf0: - hs->p_lock = c; - pref |= PRE_LOCK; - break; - case 0x26: case 0x2e: case 0x36: - case 0x3e: case 0x64: case 0x65: - hs->p_seg = c; - pref |= PRE_SEG; - break; - case 0x66: - hs->p_66 = c; - pref |= PRE_66; - break; - case 0x67: - hs->p_67 = c; - pref |= PRE_67; - break; - default: - goto pref_done; - } - pref_done: - - hs->flags = (uint32_t)pref << 23; - - if (!pref) - pref |= PRE_NONE; - - if ((c & 0xf0) == 0x40) { - hs->flags |= F_PREFIX_REX; - if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) - op64++; - hs->rex_r = (c & 7) >> 2; - hs->rex_x = (c & 3) >> 1; - hs->rex_b = c & 1; - if (((c = *p++) & 0xf0) == 0x40) { - opcode = c; - goto error_opcode; - } - } - - if ((hs->opcode = c) == 0x0f) { - hs->opcode2 = c = *p++; - ht += DELTA_OPCODES; - } else if (c >= 0xa0 && c <= 0xa3) { - op64++; - if (pref & PRE_67) - pref |= PRE_66; - else - pref &= ~PRE_66; - } - - opcode = c; - cflags = ht[ht[opcode / 4] + (opcode % 4)]; - - if (cflags == C_ERROR) { - error_opcode: - hs->flags |= F_ERROR | F_ERROR_OPCODE; - cflags = 0; - if ((opcode & -3) == 0x24) - cflags++; - } - - x = 0; - if (cflags & C_GROUP) { - uint16_t t; - t = *(uint16_t *)(ht + (cflags & 0x7f)); - cflags = (uint8_t)t; - x = (uint8_t)(t >> 8); - } - - if (hs->opcode2) { - ht = hde64_table + DELTA_PREFIXES; - if (ht[ht[opcode / 4] + (opcode % 4)] & pref) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - } - - if (cflags & C_MODRM) { - hs->flags |= F_MODRM; - hs->modrm = c = *p++; - hs->modrm_mod = m_mod = c >> 6; - hs->modrm_rm = m_rm = c & 7; - hs->modrm_reg = m_reg = (c & 0x3f) >> 3; - - if (x && ((x << m_reg) & 0x80)) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - - if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { - uint8_t t = opcode - 0xd9; - if (m_mod == 3) { - ht = hde64_table + DELTA_FPU_MODRM + t*8; - t = ht[m_reg] << m_rm; - } else { - ht = hde64_table + DELTA_FPU_REG; - t = ht[t] << m_reg; - } - if (t & 0x80) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - } - - if (pref & PRE_LOCK) { - if (m_mod == 3) { - hs->flags |= F_ERROR | F_ERROR_LOCK; - } else { - uint8_t *table_end, op = opcode; - if (hs->opcode2) { - ht = hde64_table + DELTA_OP2_LOCK_OK; - table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; - } else { - ht = hde64_table + DELTA_OP_LOCK_OK; - table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; - op &= -2; - } - for (; ht != table_end; ht++) - if (*ht++ == op) { - if (!((*ht << m_reg) & 0x80)) - goto no_lock_error; - else - break; - } - hs->flags |= F_ERROR | F_ERROR_LOCK; - no_lock_error: - ; - } - } - - if (hs->opcode2) { - switch (opcode) { - case 0x20: case 0x22: - m_mod = 3; - if (m_reg > 4 || m_reg == 1) - goto error_operand; - else - goto no_error_operand; - case 0x21: case 0x23: - m_mod = 3; - if (m_reg == 4 || m_reg == 5) - goto error_operand; - else - goto no_error_operand; - } - } else { - switch (opcode) { - case 0x8c: - if (m_reg > 5) - goto error_operand; - else - goto no_error_operand; - case 0x8e: - if (m_reg == 1 || m_reg > 5) - goto error_operand; - else - goto no_error_operand; - } - } - - if (m_mod == 3) { - uint8_t *table_end; - if (hs->opcode2) { - ht = hde64_table + DELTA_OP2_ONLY_MEM; - table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; - } else { - ht = hde64_table + DELTA_OP_ONLY_MEM; - table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; - } - for (; ht != table_end; ht += 2) - if (*ht++ == opcode) { - if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) - goto error_operand; - else - break; - } - goto no_error_operand; - } else if (hs->opcode2) { - switch (opcode) { - case 0x50: case 0xd7: case 0xf7: - if (pref & (PRE_NONE | PRE_66)) - goto error_operand; - break; - case 0xd6: - if (pref & (PRE_F2 | PRE_F3)) - goto error_operand; - break; - case 0xc5: - goto error_operand; - } - goto no_error_operand; - } else - goto no_error_operand; - - error_operand: - hs->flags |= F_ERROR | F_ERROR_OPERAND; - no_error_operand: - - c = *p++; - if (m_reg <= 1) { - if (opcode == 0xf6) - cflags |= C_IMM8; - else if (opcode == 0xf7) - cflags |= C_IMM_P66; - } - - switch (m_mod) { - case 0: - if (pref & PRE_67) { - if (m_rm == 6) - disp_size = 2; - } else - if (m_rm == 5) - disp_size = 4; - break; - case 1: - disp_size = 1; - break; - case 2: - disp_size = 2; - if (!(pref & PRE_67)) - disp_size <<= 1; - break; - } - - if (m_mod != 3 && m_rm == 4) { - hs->flags |= F_SIB; - p++; - hs->sib = c; - hs->sib_scale = c >> 6; - hs->sib_index = (c & 0x3f) >> 3; - if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) - disp_size = 4; - } - - p--; - switch (disp_size) { - case 1: - hs->flags |= F_DISP8; - hs->disp.disp8 = *p; - break; - case 2: - hs->flags |= F_DISP16; - hs->disp.disp16 = *(uint16_t *)p; - break; - case 4: - hs->flags |= F_DISP32; - hs->disp.disp32 = *(uint32_t *)p; - break; - } - p += disp_size; - } else if (pref & PRE_LOCK) - hs->flags |= F_ERROR | F_ERROR_LOCK; - - if (cflags & C_IMM_P66) { - if (cflags & C_REL32) { - if (pref & PRE_66) { - hs->flags |= F_IMM16 | F_RELATIVE; - hs->imm.imm16 = *(uint16_t *)p; - p += 2; - goto disasm_done; - } - goto rel32_ok; - } - if (op64) { - hs->flags |= F_IMM64; - hs->imm.imm64 = *(uint64_t *)p; - p += 8; - } else if (!(pref & PRE_66)) { - hs->flags |= F_IMM32; - hs->imm.imm32 = *(uint32_t *)p; - p += 4; - } else - goto imm16_ok; - } - - - if (cflags & C_IMM16) { - imm16_ok: - hs->flags |= F_IMM16; - hs->imm.imm16 = *(uint16_t *)p; - p += 2; - } - if (cflags & C_IMM8) { - hs->flags |= F_IMM8; - hs->imm.imm8 = *p++; - } - - if (cflags & C_REL32) { - rel32_ok: - hs->flags |= F_IMM32 | F_RELATIVE; - hs->imm.imm32 = *(uint32_t *)p; - p += 4; - } else if (cflags & C_REL8) { - hs->flags |= F_IMM8 | F_RELATIVE; - hs->imm.imm8 = *p++; - } - - disasm_done: - - if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { - hs->flags |= F_ERROR | F_ERROR_LENGTH; - hs->len = 15; - } - - return (unsigned int)hs->len; -} - -#endif // defined(_M_X64) || defined(__x86_64__) diff --git a/third_party/minhook/src/hde/hde64.h b/third_party/minhook/src/hde/hde64.h deleted file mode 100644 index ecbf4df..0000000 --- a/third_party/minhook/src/hde/hde64.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Hacker Disassembler Engine 64 - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - * hde64.h: C/C++ header file - * - */ - -#ifndef _HDE64_H_ -#define _HDE64_H_ - -/* stdint.h - C99 standard header - * http://en.wikipedia.org/wiki/stdint.h - * - * if your compiler doesn't contain "stdint.h" header (for - * example, Microsoft Visual C++), you can download file: - * http://www.azillionmonkeys.com/qed/pstdint.h - * and change next line to: - * #include "pstdint.h" - */ -#include "pstdint.h" - -#define F_MODRM 0x00000001 -#define F_SIB 0x00000002 -#define F_IMM8 0x00000004 -#define F_IMM16 0x00000008 -#define F_IMM32 0x00000010 -#define F_IMM64 0x00000020 -#define F_DISP8 0x00000040 -#define F_DISP16 0x00000080 -#define F_DISP32 0x00000100 -#define F_RELATIVE 0x00000200 -#define F_ERROR 0x00001000 -#define F_ERROR_OPCODE 0x00002000 -#define F_ERROR_LENGTH 0x00004000 -#define F_ERROR_LOCK 0x00008000 -#define F_ERROR_OPERAND 0x00010000 -#define F_PREFIX_REPNZ 0x01000000 -#define F_PREFIX_REPX 0x02000000 -#define F_PREFIX_REP 0x03000000 -#define F_PREFIX_66 0x04000000 -#define F_PREFIX_67 0x08000000 -#define F_PREFIX_LOCK 0x10000000 -#define F_PREFIX_SEG 0x20000000 -#define F_PREFIX_REX 0x40000000 -#define F_PREFIX_ANY 0x7f000000 - -#define PREFIX_SEGMENT_CS 0x2e -#define PREFIX_SEGMENT_SS 0x36 -#define PREFIX_SEGMENT_DS 0x3e -#define PREFIX_SEGMENT_ES 0x26 -#define PREFIX_SEGMENT_FS 0x64 -#define PREFIX_SEGMENT_GS 0x65 -#define PREFIX_LOCK 0xf0 -#define PREFIX_REPNZ 0xf2 -#define PREFIX_REPX 0xf3 -#define PREFIX_OPERAND_SIZE 0x66 -#define PREFIX_ADDRESS_SIZE 0x67 - -#pragma pack(push,1) - -typedef struct { - uint8_t len; - uint8_t p_rep; - uint8_t p_lock; - uint8_t p_seg; - uint8_t p_66; - uint8_t p_67; - uint8_t rex; - uint8_t rex_w; - uint8_t rex_r; - uint8_t rex_x; - uint8_t rex_b; - uint8_t opcode; - uint8_t opcode2; - uint8_t modrm; - uint8_t modrm_mod; - uint8_t modrm_reg; - uint8_t modrm_rm; - uint8_t sib; - uint8_t sib_scale; - uint8_t sib_index; - uint8_t sib_base; - union { - uint8_t imm8; - uint16_t imm16; - uint32_t imm32; - uint64_t imm64; - } imm; - union { - uint8_t disp8; - uint16_t disp16; - uint32_t disp32; - } disp; - uint32_t flags; -} hde64s; - -#pragma pack(pop) - -#ifdef __cplusplus -extern "C" { -#endif - -/* __cdecl */ -unsigned int hde64_disasm(const void *code, hde64s *hs); - -#ifdef __cplusplus -} -#endif - -#endif /* _HDE64_H_ */ diff --git a/third_party/minhook/src/hde/pstdint.h b/third_party/minhook/src/hde/pstdint.h deleted file mode 100644 index 84d82a0..0000000 --- a/third_party/minhook/src/hde/pstdint.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include <windows.h> - -// Integer types for HDE. -typedef INT8 int8_t; -typedef INT16 int16_t; -typedef INT32 int32_t; -typedef INT64 int64_t; -typedef UINT8 uint8_t; -typedef UINT16 uint16_t; -typedef UINT32 uint32_t; -typedef UINT64 uint64_t; diff --git a/third_party/minhook/src/hde/table64.h b/third_party/minhook/src/hde/table64.h deleted file mode 100644 index 01d4541..0000000 --- a/third_party/minhook/src/hde/table64.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Hacker Disassembler Engine 64 C - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - */ - -#define C_NONE 0x00 -#define C_MODRM 0x01 -#define C_IMM8 0x02 -#define C_IMM16 0x04 -#define C_IMM_P66 0x10 -#define C_REL8 0x20 -#define C_REL32 0x40 -#define C_GROUP 0x80 -#define C_ERROR 0xff - -#define PRE_ANY 0x00 -#define PRE_NONE 0x01 -#define PRE_F2 0x02 -#define PRE_F3 0x04 -#define PRE_66 0x08 -#define PRE_67 0x10 -#define PRE_LOCK 0x20 -#define PRE_SEG 0x40 -#define PRE_ALL 0xff - -#define DELTA_OPCODES 0x4a -#define DELTA_FPU_REG 0xfd -#define DELTA_FPU_MODRM 0x104 -#define DELTA_PREFIXES 0x13c -#define DELTA_OP_LOCK_OK 0x1ae -#define DELTA_OP2_LOCK_OK 0x1c6 -#define DELTA_OP_ONLY_MEM 0x1d8 -#define DELTA_OP2_ONLY_MEM 0x1e7 - -unsigned char hde64_table[] = { - 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, - 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, - 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, - 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, - 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, - 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, - 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, - 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, - 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, - 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, - 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, - 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, - 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, - 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, - 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, - 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, - 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, - 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, - 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, - 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, - 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, - 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, - 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, - 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, - 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, - 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, - 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, - 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, - 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, - 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, - 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, - 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, - 0x00,0xf0,0x02,0x00 -}; diff --git a/third_party/minhook/src/hook.c b/third_party/minhook/src/hook.c deleted file mode 100644 index 06f5696..0000000 --- a/third_party/minhook/src/hook.c +++ /dev/null @@ -1,939 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include <windows.h> -#include <tlhelp32.h> -#include <limits.h> - -#include "../include/MinHook.h" -#include "buffer.h" -#include "trampoline.h" - -#ifndef ARRAYSIZE - #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - -// Initial capacity of the HOOK_ENTRY buffer. -#define INITIAL_HOOK_CAPACITY 32 - -// Initial capacity of the thread IDs buffer. -#define INITIAL_THREAD_CAPACITY 128 - -// Special hook position values. -#define INVALID_HOOK_POS UINT_MAX -#define ALL_HOOKS_POS UINT_MAX - -// Freeze() action argument defines. -#define ACTION_DISABLE 0 -#define ACTION_ENABLE 1 -#define ACTION_APPLY_QUEUED 2 - -// Thread access rights for suspending/resuming threads. -#define THREAD_ACCESS \ - (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT) - -// Hook information. -typedef struct _HOOK_ENTRY -{ - LPVOID pTarget; // Address of the target function. - LPVOID pDetour; // Address of the detour or relay function. - LPVOID pTrampoline; // Address of the trampoline function. - UINT8 backup[8]; // Original prologue of the target function. - - UINT8 patchAbove : 1; // Uses the hot patch area. - UINT8 isEnabled : 1; // Enabled. - UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled. - - UINT nIP : 4; // Count of the instruction boundaries. - UINT8 oldIPs[8]; // Instruction boundaries of the target function. - UINT8 newIPs[8]; // Instruction boundaries of the trampoline function. -} HOOK_ENTRY, *PHOOK_ENTRY; - -// Suspended threads for Freeze()/Unfreeze(). -typedef struct _FROZEN_THREADS -{ - LPDWORD pItems; // Data heap - UINT capacity; // Size of allocated data heap, items - UINT size; // Actual number of data items -} FROZEN_THREADS, *PFROZEN_THREADS; - -//------------------------------------------------------------------------- -// Global Variables: -//------------------------------------------------------------------------- - -// Spin lock flag for EnterSpinLock()/LeaveSpinLock(). -static volatile LONG g_isLocked = FALSE; - -// Private heap handle. If not NULL, this library is initialized. -static HANDLE g_hHeap = NULL; - -// Hook entries. -static struct -{ - PHOOK_ENTRY pItems; // Data heap - UINT capacity; // Size of allocated data heap, items - UINT size; // Actual number of data items -} g_hooks; - -//------------------------------------------------------------------------- -// Returns INVALID_HOOK_POS if not found. -static UINT FindHookEntry(LPVOID pTarget) -{ - UINT i; - for (i = 0; i < g_hooks.size; ++i) - { - if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget) - return i; - } - - return INVALID_HOOK_POS; -} - -//------------------------------------------------------------------------- -static PHOOK_ENTRY AddHookEntry() -{ - if (g_hooks.pItems == NULL) - { - g_hooks.capacity = INITIAL_HOOK_CAPACITY; - g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc( - g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY)); - if (g_hooks.pItems == NULL) - return NULL; - } - else if (g_hooks.size >= g_hooks.capacity) - { - PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( - g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY)); - if (p == NULL) - return NULL; - - g_hooks.capacity *= 2; - g_hooks.pItems = p; - } - - return &g_hooks.pItems[g_hooks.size++]; -} - -//------------------------------------------------------------------------- -static VOID DeleteHookEntry(UINT pos) -{ - if (pos < g_hooks.size - 1) - g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1]; - - g_hooks.size--; - - if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size) - { - PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( - g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY)); - if (p == NULL) - return; - - g_hooks.capacity /= 2; - g_hooks.pItems = p; - } -} - -//------------------------------------------------------------------------- -static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip) -{ - UINT i; - - if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL))) - return (DWORD_PTR)pHook->pTarget; - - for (i = 0; i < pHook->nIP; ++i) - { - if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i])) - return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]; - } - -#if defined(_M_X64) || defined(__x86_64__) - // Check relay function. - if (ip == (DWORD_PTR)pHook->pDetour) - return (DWORD_PTR)pHook->pTarget; -#endif - - return 0; -} - -//------------------------------------------------------------------------- -static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip) -{ - UINT i; - for (i = 0; i < pHook->nIP; ++i) - { - if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i])) - return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]; - } - - return 0; -} - -//------------------------------------------------------------------------- -static VOID ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action) -{ - // If the thread suspended in the overwritten area, - // move IP to the proper address. - - CONTEXT c; -#if defined(_M_X64) || defined(__x86_64__) - DWORD64 *pIP = &c.Rip; -#else - DWORD *pIP = &c.Eip; -#endif - UINT count; - - c.ContextFlags = CONTEXT_CONTROL; - if (!GetThreadContext(hThread, &c)) - return; - - if (pos == ALL_HOOKS_POS) - { - pos = 0; - count = g_hooks.size; - } - else - { - count = pos + 1; - } - - for (; pos < count; ++pos) - { - PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; - BOOL enable; - DWORD_PTR ip; - - switch (action) - { - case ACTION_DISABLE: - enable = FALSE; - break; - - case ACTION_ENABLE: - enable = TRUE; - break; - - default: // ACTION_APPLY_QUEUED - enable = pHook->queueEnable; - break; - } - if (pHook->isEnabled == enable) - continue; - - if (enable) - ip = FindNewIP(pHook, *pIP); - else - ip = FindOldIP(pHook, *pIP); - - if (ip != 0) - { - *pIP = ip; - SetThreadContext(hThread, &c); - } - } -} - -//------------------------------------------------------------------------- -static BOOL EnumerateThreads(PFROZEN_THREADS pThreads) -{ - BOOL succeeded = FALSE; - - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (hSnapshot != INVALID_HANDLE_VALUE) - { - THREADENTRY32 te; - te.dwSize = sizeof(THREADENTRY32); - if (Thread32First(hSnapshot, &te)) - { - succeeded = TRUE; - do - { - if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD)) - && te.th32OwnerProcessID == GetCurrentProcessId() - && te.th32ThreadID != GetCurrentThreadId()) - { - if (pThreads->pItems == NULL) - { - pThreads->capacity = INITIAL_THREAD_CAPACITY; - pThreads->pItems - = (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD)); - if (pThreads->pItems == NULL) - { - succeeded = FALSE; - break; - } - } - else if (pThreads->size >= pThreads->capacity) - { - LPDWORD p; - pThreads->capacity *= 2; - p = (LPDWORD)HeapReAlloc( - g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(DWORD)); - if (p == NULL) - { - succeeded = FALSE; - break; - } - - pThreads->pItems = p; - } - pThreads->pItems[pThreads->size++] = te.th32ThreadID; - } - - te.dwSize = sizeof(THREADENTRY32); - } while (Thread32Next(hSnapshot, &te)); - - if (succeeded && GetLastError() != ERROR_NO_MORE_FILES) - succeeded = FALSE; - - if (!succeeded && pThreads->pItems != NULL) - { - HeapFree(g_hHeap, 0, pThreads->pItems); - pThreads->pItems = NULL; - } - } - CloseHandle(hSnapshot); - } - - return succeeded; -} - -//------------------------------------------------------------------------- -static MH_STATUS Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action) -{ - MH_STATUS status = MH_OK; - - pThreads->pItems = NULL; - pThreads->capacity = 0; - pThreads->size = 0; - if (!EnumerateThreads(pThreads)) - { - status = MH_ERROR_MEMORY_ALLOC; - } - else if (pThreads->pItems != NULL) - { - UINT i; - for (i = 0; i < pThreads->size; ++i) - { - HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]); - BOOL suspended = FALSE; - if (hThread != NULL) - { - DWORD result = SuspendThread(hThread); - if (result != 0xFFFFFFFF) - { - suspended = TRUE; - ProcessThreadIPs(hThread, pos, action); - } - CloseHandle(hThread); - } - - if (!suspended) - { - // Mark thread as not suspended, so it's not resumed later on. - pThreads->pItems[i] = 0; - } - } - } - - return status; -} - -//------------------------------------------------------------------------- -static VOID Unfreeze(PFROZEN_THREADS pThreads) -{ - if (pThreads->pItems != NULL) - { - UINT i; - for (i = 0; i < pThreads->size; ++i) - { - DWORD threadId = pThreads->pItems[i]; - if (threadId != 0) - { - HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId); - if (hThread != NULL) - { - ResumeThread(hThread); - CloseHandle(hThread); - } - } - } - - HeapFree(g_hHeap, 0, pThreads->pItems); - } -} - -//------------------------------------------------------------------------- -static MH_STATUS EnableHookLL(UINT pos, BOOL enable) -{ - PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; - DWORD oldProtect; - SIZE_T patchSize = sizeof(JMP_REL); - LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget; - - if (pHook->patchAbove) - { - pPatchTarget -= sizeof(JMP_REL); - patchSize += sizeof(JMP_REL_SHORT); - } - - if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect)) - return MH_ERROR_MEMORY_PROTECT; - - if (enable) - { - PJMP_REL pJmp = (PJMP_REL)pPatchTarget; - pJmp->opcode = 0xE9; - pJmp->operand = (INT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL))); - - if (pHook->patchAbove) - { - PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget; - pShortJmp->opcode = 0xEB; - pShortJmp->operand = (INT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL))); - } - } - else - { - if (pHook->patchAbove) - memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); - else - memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL)); - } - - VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect); - - // Just-in-case measure. - FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize); - - pHook->isEnabled = enable; - pHook->queueEnable = enable; - - return MH_OK; -} - -//------------------------------------------------------------------------- -static MH_STATUS EnableAllHooksLL(BOOL enable) -{ - MH_STATUS status = MH_OK; - UINT i, first = INVALID_HOOK_POS; - - for (i = 0; i < g_hooks.size; ++i) - { - if (g_hooks.pItems[i].isEnabled != enable) - { - first = i; - break; - } - } - - if (first != INVALID_HOOK_POS) - { - FROZEN_THREADS threads; - status = Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE); - if (status == MH_OK) - { - for (i = first; i < g_hooks.size; ++i) - { - if (g_hooks.pItems[i].isEnabled != enable) - { - status = EnableHookLL(i, enable); - if (status != MH_OK) - break; - } - } - - Unfreeze(&threads); - } - } - - return status; -} - -//------------------------------------------------------------------------- -static VOID EnterSpinLock(VOID) -{ - SIZE_T spinCount = 0; - - // Wait until the flag is FALSE. - while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE) - { - // No need to generate a memory barrier here, since InterlockedCompareExchange() - // generates a full memory barrier itself. - - // Prevent the loop from being too busy. - if (spinCount < 32) - Sleep(0); - else - Sleep(1); - - spinCount++; - } -} - -//------------------------------------------------------------------------- -static VOID LeaveSpinLock(VOID) -{ - // No need to generate a memory barrier here, since InterlockedExchange() - // generates a full memory barrier itself. - - InterlockedExchange(&g_isLocked, FALSE); -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_Initialize(VOID) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap == NULL) - { - g_hHeap = HeapCreate(0, 0, 0); - if (g_hHeap != NULL) - { - // Initialize the internal function buffer. - InitializeBuffer(); - } - else - { - status = MH_ERROR_MEMORY_ALLOC; - } - } - else - { - status = MH_ERROR_ALREADY_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_Uninitialize(VOID) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - status = EnableAllHooksLL(FALSE); - if (status == MH_OK) - { - // Free the internal function buffer. - - // HeapFree is actually not required, but some tools detect a false - // memory leak without HeapFree. - - UninitializeBuffer(); - - HeapFree(g_hHeap, 0, g_hooks.pItems); - HeapDestroy(g_hHeap); - - g_hHeap = NULL; - - g_hooks.pItems = NULL; - g_hooks.capacity = 0; - g_hooks.size = 0; - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour)) - { - UINT pos = FindHookEntry(pTarget); - if (pos == INVALID_HOOK_POS) - { - LPVOID pBuffer = AllocateBuffer(pTarget); - if (pBuffer != NULL) - { - TRAMPOLINE ct; - - ct.pTarget = pTarget; - ct.pDetour = pDetour; - ct.pTrampoline = pBuffer; - if (CreateTrampolineFunction(&ct)) - { - PHOOK_ENTRY pHook = AddHookEntry(); - if (pHook != NULL) - { - pHook->pTarget = ct.pTarget; -#if defined(_M_X64) || defined(__x86_64__) - pHook->pDetour = ct.pRelay; -#else - pHook->pDetour = ct.pDetour; -#endif - pHook->pTrampoline = ct.pTrampoline; - pHook->patchAbove = ct.patchAbove; - pHook->isEnabled = FALSE; - pHook->queueEnable = FALSE; - pHook->nIP = ct.nIP; - memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs)); - memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs)); - - // Back up the target function. - - if (ct.patchAbove) - { - memcpy( - pHook->backup, - (LPBYTE)pTarget - sizeof(JMP_REL), - sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); - } - else - { - memcpy(pHook->backup, pTarget, sizeof(JMP_REL)); - } - - if (ppOriginal != NULL) - *ppOriginal = pHook->pTrampoline; - } - else - { - status = MH_ERROR_MEMORY_ALLOC; - } - } - else - { - status = MH_ERROR_UNSUPPORTED_FUNCTION; - } - - if (status != MH_OK) - { - FreeBuffer(pBuffer); - } - } - else - { - status = MH_ERROR_MEMORY_ALLOC; - } - } - else - { - status = MH_ERROR_ALREADY_CREATED; - } - } - else - { - status = MH_ERROR_NOT_EXECUTABLE; - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - UINT pos = FindHookEntry(pTarget); - if (pos != INVALID_HOOK_POS) - { - if (g_hooks.pItems[pos].isEnabled) - { - FROZEN_THREADS threads; - status = Freeze(&threads, pos, ACTION_DISABLE); - if (status == MH_OK) - { - status = EnableHookLL(pos, FALSE); - - Unfreeze(&threads); - } - } - - if (status == MH_OK) - { - FreeBuffer(g_hooks.pItems[pos].pTrampoline); - DeleteHookEntry(pos); - } - } - else - { - status = MH_ERROR_NOT_CREATED; - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - if (pTarget == MH_ALL_HOOKS) - { - status = EnableAllHooksLL(enable); - } - else - { - UINT pos = FindHookEntry(pTarget); - if (pos != INVALID_HOOK_POS) - { - if (g_hooks.pItems[pos].isEnabled != enable) - { - FROZEN_THREADS threads; - status = Freeze(&threads, pos, ACTION_ENABLE); - if (status == MH_OK) - { - status = EnableHookLL(pos, enable); - - Unfreeze(&threads); - } - } - else - { - status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED; - } - } - else - { - status = MH_ERROR_NOT_CREATED; - } - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget) -{ - return EnableHook(pTarget, TRUE); -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget) -{ - return EnableHook(pTarget, FALSE); -} - -//------------------------------------------------------------------------- -static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable) -{ - MH_STATUS status = MH_OK; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - if (pTarget == MH_ALL_HOOKS) - { - UINT i; - for (i = 0; i < g_hooks.size; ++i) - g_hooks.pItems[i].queueEnable = queueEnable; - } - else - { - UINT pos = FindHookEntry(pTarget); - if (pos != INVALID_HOOK_POS) - { - g_hooks.pItems[pos].queueEnable = queueEnable; - } - else - { - status = MH_ERROR_NOT_CREATED; - } - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget) -{ - return QueueHook(pTarget, TRUE); -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget) -{ - return QueueHook(pTarget, FALSE); -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_ApplyQueued(VOID) -{ - MH_STATUS status = MH_OK; - UINT i, first = INVALID_HOOK_POS; - - EnterSpinLock(); - - if (g_hHeap != NULL) - { - for (i = 0; i < g_hooks.size; ++i) - { - if (g_hooks.pItems[i].isEnabled != g_hooks.pItems[i].queueEnable) - { - first = i; - break; - } - } - - if (first != INVALID_HOOK_POS) - { - FROZEN_THREADS threads; - status = Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED); - if (status == MH_OK) - { - for (i = first; i < g_hooks.size; ++i) - { - PHOOK_ENTRY pHook = &g_hooks.pItems[i]; - if (pHook->isEnabled != pHook->queueEnable) - { - status = EnableHookLL(i, pHook->queueEnable); - if (status != MH_OK) - break; - } - } - - Unfreeze(&threads); - } - } - } - else - { - status = MH_ERROR_NOT_INITIALIZED; - } - - LeaveSpinLock(); - - return status; -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_CreateHookApiEx( - LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, - LPVOID *ppOriginal, LPVOID *ppTarget) -{ - HMODULE hModule; - LPVOID pTarget; - - hModule = GetModuleHandleW(pszModule); - if (hModule == NULL) - return MH_ERROR_MODULE_NOT_FOUND; - - pTarget = (LPVOID)GetProcAddress(hModule, pszProcName); - if (pTarget == NULL) - return MH_ERROR_FUNCTION_NOT_FOUND; - - if (ppTarget != NULL) - *ppTarget = pTarget; - - return MH_CreateHook(pTarget, pDetour, ppOriginal); -} - -//------------------------------------------------------------------------- -MH_STATUS WINAPI MH_CreateHookApi( - LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal) -{ - return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL); -} - -//------------------------------------------------------------------------- -const char *WINAPI MH_StatusToString(MH_STATUS status) -{ -#define MH_ST2STR(x) \ - case x: \ - return #x; - - switch (status) { - MH_ST2STR(MH_UNKNOWN) - MH_ST2STR(MH_OK) - MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED) - MH_ST2STR(MH_ERROR_NOT_INITIALIZED) - MH_ST2STR(MH_ERROR_ALREADY_CREATED) - MH_ST2STR(MH_ERROR_NOT_CREATED) - MH_ST2STR(MH_ERROR_ENABLED) - MH_ST2STR(MH_ERROR_DISABLED) - MH_ST2STR(MH_ERROR_NOT_EXECUTABLE) - MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION) - MH_ST2STR(MH_ERROR_MEMORY_ALLOC) - MH_ST2STR(MH_ERROR_MEMORY_PROTECT) - MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND) - MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND) - } - -#undef MH_ST2STR - - return "(unknown)"; -} diff --git a/third_party/minhook/src/trampoline.c b/third_party/minhook/src/trampoline.c deleted file mode 100644 index cf8ce05..0000000 --- a/third_party/minhook/src/trampoline.c +++ /dev/null @@ -1,320 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include <windows.h> - -#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS) - #define ALLOW_INTRINSICS - #include <intrin.h> -#endif - -#ifndef ARRAYSIZE - #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - -#if defined(_M_X64) || defined(__x86_64__) - #include "./hde/hde64.h" - typedef hde64s HDE; - #define HDE_DISASM(code, hs) hde64_disasm(code, hs) -#else - #include "./hde/hde32.h" - typedef hde32s HDE; - #define HDE_DISASM(code, hs) hde32_disasm(code, hs) -#endif - -#include "trampoline.h" -#include "buffer.h" - -// Maximum size of a trampoline function. -#if defined(_M_X64) || defined(__x86_64__) - #define TRAMPOLINE_MAX_SIZE (MEMORY_SLOT_SIZE - sizeof(JMP_ABS)) -#else - #define TRAMPOLINE_MAX_SIZE MEMORY_SLOT_SIZE -#endif - -//------------------------------------------------------------------------- -static BOOL IsCodePadding(LPBYTE pInst, UINT size) -{ - UINT i; - - if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC) - return FALSE; - - for (i = 1; i < size; ++i) - { - if (pInst[i] != pInst[0]) - return FALSE; - } - return TRUE; -} - -//------------------------------------------------------------------------- -BOOL CreateTrampolineFunction(PTRAMPOLINE ct) -{ -#if defined(_M_X64) || defined(__x86_64__) - CALL_ABS call = { - 0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8] - 0xEB, 0x08, // EB 08: JMP +10 - 0x0000000000000000ULL // Absolute destination address - }; - JMP_ABS jmp = { - 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] - 0x0000000000000000ULL // Absolute destination address - }; - JCC_ABS jcc = { - 0x70, 0x0E, // 7* 0E: J** +16 - 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] - 0x0000000000000000ULL // Absolute destination address - }; -#else - CALL_REL call = { - 0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx - 0x00000000 // Relative destination address - }; - JMP_REL jmp = { - 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx - 0x00000000 // Relative destination address - }; - JCC_REL jcc = { - 0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx - 0x00000000 // Relative destination address - }; -#endif - - UINT8 oldPos = 0; - UINT8 newPos = 0; - ULONG_PTR jmpDest = 0; // Destination address of an internal jump. - BOOL finished = FALSE; // Is the function completed? -#if defined(_M_X64) || defined(__x86_64__) - UINT8 instBuf[16]; -#endif - - ct->patchAbove = FALSE; - ct->nIP = 0; - - do - { - HDE hs; - UINT copySize; - LPVOID pCopySrc; - ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos; - ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos; - - copySize = HDE_DISASM((LPVOID)pOldInst, &hs); - if (hs.flags & F_ERROR) - return FALSE; - - pCopySrc = (LPVOID)pOldInst; - if (oldPos >= sizeof(JMP_REL)) - { - // The trampoline function is long enough. - // Complete the function with the jump to the target function. -#if defined(_M_X64) || defined(__x86_64__) - jmp.address = pOldInst; -#else - jmp.operand = (INT32)(pOldInst - (pNewInst + sizeof(jmp))); -#endif - pCopySrc = &jmp; - copySize = sizeof(jmp); - - finished = TRUE; - } -#if defined(_M_X64) || defined(__x86_64__) - else if ((hs.modrm & 0xC7) == 0x05) - { - // Instructions using RIP relative addressing. (ModR/M = 00???101B) - - // Modify the RIP relative address. - PUINT32 pRelAddr; - - // Avoid using memcpy to reduce the footprint. -#ifndef ALLOW_INTRINSICS - memcpy(instBuf, (LPBYTE)pOldInst, copySize); -#else - __movsb(instBuf, (LPBYTE)pOldInst, copySize); -#endif - pCopySrc = instBuf; - - // Relative address is stored at (instruction length - immediate value length - 4). - pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4); - *pRelAddr - = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len)); - - // Complete the function if JMP (FF /4). - if (hs.opcode == 0xFF && hs.modrm_reg == 4) - finished = TRUE; - } -#endif - else if (hs.opcode == 0xE8) - { - // Direct relative CALL - ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32; -#if defined(_M_X64) || defined(__x86_64__) - call.address = dest; -#else - call.operand = (INT32)(dest - (pNewInst + sizeof(call))); -#endif - pCopySrc = &call; - copySize = sizeof(call); - } - else if ((hs.opcode & 0xFD) == 0xE9) - { - // Direct relative JMP (EB or E9) - ULONG_PTR dest = pOldInst + hs.len; - - if (hs.opcode == 0xEB) // isShort jmp - dest += (INT8)hs.imm.imm8; - else - dest += (INT32)hs.imm.imm32; - - // Simply copy an internal jump. - if ((ULONG_PTR)ct->pTarget <= dest - && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) - { - if (jmpDest < dest) - jmpDest = dest; - } - else - { -#if defined(_M_X64) || defined(__x86_64__) - jmp.address = dest; -#else - jmp.operand = (INT32)(dest - (pNewInst + sizeof(jmp))); -#endif - pCopySrc = &jmp; - copySize = sizeof(jmp); - - // Exit the function if it is not in the branch. - finished = (pOldInst >= jmpDest); - } - } - else if ((hs.opcode & 0xF0) == 0x70 - || (hs.opcode & 0xFC) == 0xE0 - || (hs.opcode2 & 0xF0) == 0x80) - { - // Direct relative Jcc - ULONG_PTR dest = pOldInst + hs.len; - - if ((hs.opcode & 0xF0) == 0x70 // Jcc - || (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ - dest += (INT8)hs.imm.imm8; - else - dest += (INT32)hs.imm.imm32; - - // Simply copy an internal jump. - if ((ULONG_PTR)ct->pTarget <= dest - && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) - { - if (jmpDest < dest) - jmpDest = dest; - } - else if ((hs.opcode & 0xFC) == 0xE0) - { - // LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported. - return FALSE; - } - else - { - UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); -#if defined(_M_X64) || defined(__x86_64__) - // Invert the condition in x64 mode to simplify the conditional jump logic. - jcc.opcode = 0x71 ^ cond; - jcc.address = dest; -#else - jcc.opcode1 = 0x80 | cond; - jcc.operand = (INT32)(dest - (pNewInst + sizeof(jcc))); -#endif - pCopySrc = &jcc; - copySize = sizeof(jcc); - } - } - else if ((hs.opcode & 0xFE) == 0xC2) - { - // RET (C2 or C3) - - // Complete the function if not in a branch. - finished = (pOldInst >= jmpDest); - } - - // Can't alter the instruction length in a branch. - if (pOldInst < jmpDest && copySize != hs.len) - return FALSE; - - // Trampoline function is too large. - if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE) - return FALSE; - - // Trampoline function has too many instructions. - if (ct->nIP >= ARRAYSIZE(ct->oldIPs)) - return FALSE; - - ct->oldIPs[ct->nIP] = oldPos; - ct->newIPs[ct->nIP] = newPos; - ct->nIP++; - - // Avoid using memcpy to reduce the footprint. -#ifndef ALLOW_INTRINSICS - memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize); -#else - __movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize); -#endif - newPos += copySize; - oldPos += hs.len; - } while (!finished); - - // Is there enough place for a long jump? - if (oldPos < sizeof(JMP_REL) - && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos)) - { - // Is there enough place for a short jump? - if (oldPos < sizeof(JMP_REL_SHORT) - && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) - { - return FALSE; - } - - // Can we place the long jump above the function? - if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL))) - return FALSE; - - if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL))) - return FALSE; - - ct->patchAbove = TRUE; - } - -#if defined(_M_X64) || defined(__x86_64__) - // Create a relay function. - jmp.address = (ULONG_PTR)ct->pDetour; - - ct->pRelay = (LPBYTE)ct->pTrampoline + newPos; - memcpy(ct->pRelay, &jmp, sizeof(jmp)); -#endif - - return TRUE; -} diff --git a/third_party/minhook/src/trampoline.h b/third_party/minhook/src/trampoline.h deleted file mode 100644 index f7efb36..0000000 --- a/third_party/minhook/src/trampoline.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * MinHook - The Minimalistic API Hooking Library for x64/x86 - * Copyright (C) 2009-2017 Tsuda Kageyu. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER - * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#pragma pack(push, 1) - -// Structs for writing x86/x64 instructions. - -// 8-bit relative jump. -typedef struct _JMP_REL_SHORT -{ - UINT8 opcode; // EB xx: JMP +2+xx - INT8 operand; // Relative destination address -} JMP_REL_SHORT, *PJMP_REL_SHORT; - -// 32-bit direct relative jump/call. -typedef struct _JMP_REL -{ - UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx - INT32 operand; // Relative destination address -} JMP_REL, *PJMP_REL, CALL_REL; - -// 64-bit indirect absolute jump. -typedef struct _JMP_ABS -{ - UINT8 opcode0; // FF25 00000000: JMP [+6] - UINT8 opcode1; - UINT32 dummy; - UINT64 address; // Absolute destination address -} JMP_ABS, *PJMP_ABS; - -// 64-bit indirect absolute call. -typedef struct _CALL_ABS -{ - UINT8 opcode0; // FF15 00000002: CALL [+6] - UINT8 opcode1; - UINT32 dummy0; - UINT8 dummy1; // EB 08: JMP +10 - UINT8 dummy2; - UINT64 address; // Absolute destination address -} CALL_ABS; - -// 32-bit direct relative conditional jumps. -typedef struct _JCC_REL -{ - UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx - UINT8 opcode1; - INT32 operand; // Relative destination address -} JCC_REL; - -// 64bit indirect absolute conditional jumps that x64 lacks. -typedef struct _JCC_ABS -{ - UINT8 opcode; // 7* 0E: J** +16 - UINT8 dummy0; - UINT8 dummy1; // FF25 00000000: JMP [+6] - UINT8 dummy2; - UINT32 dummy3; - UINT64 address; // Absolute destination address -} JCC_ABS; - -#pragma pack(pop) - -typedef struct _TRAMPOLINE -{ - LPVOID pTarget; // [In] Address of the target function. - LPVOID pDetour; // [In] Address of the detour function. - LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. - -#if defined(_M_X64) || defined(__x86_64__) - LPVOID pRelay; // [Out] Address of the relay function. -#endif - BOOL patchAbove; // [Out] Should use the hot patch area? - UINT nIP; // [Out] Number of the instruction boundaries. - UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. - UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. -} TRAMPOLINE, *PTRAMPOLINE; - -BOOL CreateTrampolineFunction(PTRAMPOLINE ct); |
