aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-15 17:12:41 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-16 12:56:48 -0500
commitd55f61bd392d7e1e00dfbcb236aabfbe6f98589e (patch)
treefc1440da603a1ce49cdff6655977cee674f6da32
parentfa0579c7f5703aba9669f7f41900c67e9759fca0 (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>
-rw-r--r--src/src/CMakeLists.txt81
-rw-r--r--src/src/editexecutablesdialog.cpp13
-rw-r--r--src/src/editexecutablesdialog.ui13
-rw-r--r--src/src/executableslist.cpp12
-rw-r--r--src/src/executableslist.h4
-rw-r--r--src/src/fluorinepaths.cpp46
-rw-r--r--src/src/fluorinepaths.h15
-rw-r--r--src/src/fuseconnector.cpp60
-rw-r--r--src/src/fuseconnector.h5
-rw-r--r--src/src/organizercore.cpp16
-rw-r--r--src/src/organizercore.h13
-rw-r--r--src/src/prefixsetuprunner.cpp90
-rw-r--r--src/src/prefixsetuprunner.h1
-rw-r--r--src/src/processrunner.cpp5
-rw-r--r--src/src/protonlauncher.cpp448
-rw-r--r--src/src/spawn.cpp16
-rw-r--r--src/src/spawn.h4
-rw-r--r--src/src/vfs/fluorine_vfs_hooks.c2729
-rw-r--r--src/src/vfs/fluorine_vfs_inject.c310
-rw-r--r--src/src/vfs/vfsbridge.cpp226
-rw-r--r--src/src/vfs/vfsbridge.h28
-rw-r--r--src/src/vfs/vfsbridgepreload.c1872
-rw-r--r--third_party/minhook/AUTHORS.txt8
-rw-r--r--third_party/minhook/LICENSE.txt81
-rw-r--r--third_party/minhook/include/MinHook.h185
-rw-r--r--third_party/minhook/src/buffer.c312
-rw-r--r--third_party/minhook/src/buffer.h42
-rw-r--r--third_party/minhook/src/hde/hde64.c335
-rw-r--r--third_party/minhook/src/hde/hde64.h112
-rw-r--r--third_party/minhook/src/hde/pstdint.h39
-rw-r--r--third_party/minhook/src/hde/table64.h74
-rw-r--r--third_party/minhook/src/hook.c939
-rw-r--r--third_party/minhook/src/trampoline.c320
-rw-r--r--third_party/minhook/src/trampoline.h105
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);