diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index b5d95038b..d201e3ccb 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -29,6 +29,7 @@ $end_info$ #include "Windows/Common/Allocator.h" #include "Windows/Common/EnvironmentVariablesHandling.h" +#include "Windows/Common/FEXUnixLib.h" #include "Common/CallRetStack.h" #include "Common/JITGuardPage.h" #include "Common/Config.h" @@ -599,6 +600,7 @@ NTSTATUS ProcessInit() { FEX::Windows::SetupEnvironmentVariableValues(NtDll); FEX::Windows::Allocator::SetupHooks(NtDll); + FEX::Windows::UnixLib::Init(NtDll); { auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine); diff --git a/Source/Windows/Common/CMakeLists.txt b/Source/Windows/Common/CMakeLists.txt index 3a0a71c07..a9cdeac60 100644 --- a/Source/Windows/Common/CMakeLists.txt +++ b/Source/Windows/Common/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(CommonWindows STATIC Allocator.cpp CPUFeatures.cpp EnvironmentVariablesHandling.cpp + FEXUnixLib.cpp SHMStats.cpp InvalidationTracker.cpp ImageTracker.cpp diff --git a/Source/Windows/Common/FEXUnixLib.cpp b/Source/Windows/Common/FEXUnixLib.cpp new file mode 100644 index 000000000..9556f1302 --- /dev/null +++ b/Source/Windows/Common/FEXUnixLib.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +#include + +#include +#include +#define __WINESRC__ +#include +#include +#include "FEXUnixLib.h" +#include "../UnixLib/FEXUnixLib.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace FEX::Windows::UnixLib { +unixlib_handle_t UnixLibHandle {}; + +decltype(__wine_unix_call_dispatcher) UnixCallDispatcher {}; + +#ifdef ARCHITECTURE_arm64ec +// On ARM64EC, indirect calls go through __os_arm64x_dispatch_icall which invokes +// FEX's custom call checker. Use a naked trampoline to bypass the dispatch mechanism +// and call the unix dispatcher directly via a register branch. +static decltype(__wine_unix_call_dispatcher) UnixCallDispatcherDirect {}; + +static NTSTATUS __attribute__((naked)) TrampolineCall(unixlib_handle_t, unsigned int, void*) { + asm(R"( + adrp x16, %[Displace]; + ldr x16, [x16, #:lo12:%[Displace]] + br x16 + )" ::[Displace] "S"(&UnixCallDispatcherDirect) + : "memory"); +} +#endif + +bool Init(HMODULE NtDll) { + const auto Sym = GetProcAddress(NtDll, "__wine_unix_call_dispatcher"); + + if (!Sym) { + return false; + } + + auto TryNewWineMethod = []() { +#ifdef ARCHITECTURE_arm64ec + constexpr auto Name = "libarm64ecfex"; +#else + constexpr auto Name = "libwow64fex"; +#endif + + // Not supported in Proton at all, but supported in upstream WINE. + uint64_t Result[2]; + if (NtQueryVirtualMemory(NtCurrentProcess(), Name, MemoryWineLoadUnixLibByName, Result, sizeof(Result), nullptr)) { + return false; + } + + // Result[0] = unixlib_module_t + // Result[1] = unixlib_handle_t + // Module is ignored as it's only used to unload. + UnixLibHandle = Result[1]; + return true; + }; + + auto TryOldWineMethod = []() { + // Supported in Proton 11 and Experimental (2026-06-26). + return NtQueryVirtualMemory(NtCurrentProcess(), &__ImageBase, MemoryWineUnixFuncs, &UnixLibHandle, sizeof(UnixLibHandle), nullptr) == 0; + }; + + if (!TryNewWineMethod() && !TryOldWineMethod()) { + return false; + } + +#ifdef ARCHITECTURE_arm64ec + UnixCallDispatcherDirect = *reinterpret_cast(Sym); + UnixCallDispatcher = TrampolineCall; +#else + UnixCallDispatcher = *reinterpret_cast(Sym); +#endif + + // Give a log saying that the unix lib was loaded. + LogMan::Msg::IFmt("FEX: Loaded FEXUnixLib"); + return true; +} + +static bool UnixLibAvailable() { + return UnixLibHandle != 0; +} + +} // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/Common/FEXUnixLib.h b/Source/Windows/Common/FEXUnixLib.h new file mode 100644 index 000000000..d6a8c35e7 --- /dev/null +++ b/Source/Windows/Common/FEXUnixLib.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include +#include "wine/unixlib.h" + +namespace FEX::Windows::UnixLib { +extern decltype(__wine_unix_call_dispatcher) UnixCallDispatcher; +extern unixlib_handle_t UnixLibHandle; + +inline NTSTATUS Call(uint32_t code, void* args) { + if (!UnixCallDispatcher) { + return STATUS_NOT_SUPPORTED; + } + + return UnixCallDispatcher(UnixLibHandle, code, args); +} + +bool Init(HMODULE NtDll); +} // namespace FEX::Windows::UnixLib diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index ac23d9779..0f991c5a3 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -30,6 +30,7 @@ $end_info$ #include "Windows/Common/Allocator.h" #include "Windows/Common/EnvironmentVariablesHandling.h" +#include "Windows/Common/FEXUnixLib.h" #include "Common/CallRetStack.h" #include "Common/JITGuardPage.h" #include "Common/Config.h" @@ -527,6 +528,7 @@ void BTCpuProcessInit() { OvercommitTracker.emplace(IsWine); FEX::Windows::Allocator::SetupHooks(NtDll); + FEX::Windows::UnixLib::Init(NtDll); { auto HostFeatures = FEX::Windows::CPUFeatures::FetchHostFeatures(IsWine); diff --git a/Source/Windows/include/wine/unixlib.h b/Source/Windows/include/wine/unixlib.h index fd77405b6..20bf811ef 100644 --- a/Source/Windows/include/wine/unixlib.h +++ b/Source/Windows/include/wine/unixlib.h @@ -9,6 +9,10 @@ extern "C" { #endif +constexpr NTSTATUS STATUS_SUCCESS = 0; +constexpr NTSTATUS STATUS_NOT_SUPPORTED = 0xC00000BB; +constexpr NTSTATUS STATUS_INTERNAL_ERROR = 0xC00000E5; + typedef UINT64 unixlib_handle_t; extern NTSTATUS(WINAPI* __wine_set_unix_env)(const char*, const char*);