Files
DroidDeck/tools/linuxfs/fex/vulkanpin.c
MaxsTechReview 69c88aa344 feat: fetch Proton, FEX and the runtimes automatically, and make Linux games run under FEX (#1045)
* fix: x86 Linux games that also ship for Windows run under Proton again

Since #749 the live compatibility helper treated every installed game with
both Windows and x86 Linux builds as a user's native choice, because the
client reports an empty tool for unmapped games. Those games were left on
the Steam Linux Runtime, which cannot start them here (bwrap fails, or FEX
looks for a RootFS that is not shipped), so Valheim, Moonlighter, Half-Life 2,
TF2 and others exited within seconds.

- An unmapped game the helper never managed now gets the default Proton.
  Clearing a mapping the helper set is still kept as a native choice.
- Native choices are stored under "native_choices". The old "native" list
  also held games nobody picked, so it is dropped.

* feat: Linux builds run under FEX by default, with the runtimes fetched automatically

- Steam fetches Proton, FEX, Steam Linux Runtime 3.0 and 4.0 on the first start and keeps them updated instead of leaving them pending.
- Games that ship a Linux build go to FEX once it can run them; Windows-only games and installed Windows builds keep the Proton default.
- FEX runs games on a combined x86 runtime with the newer glibc and libstdc++ the GL and Vulkan thunks need.
- Vulkan display extensions the driver cannot handle are hidden in front of the thunk, without an audit module.
- Mono's incremental garbage collector is turned off under FEX, which stops Unity games crashing.
- x86 Steamworks finds the x86 steamclient.
- 32-bit games read the virtual controller.

* fix: controllers work in Linux Unity games such as Valheim

Joystick ioctls on an event node now fail with EINVAL like the kernel's evdev, so the SDL2 that Unity embeds reads the pad's real ID instead of ignoring it as 0000:0000.

* test: skip the switch script test without node, and the ring tests without gcc

* fix: games with a Linux build keep the Proton default

The Windows build often runs better, so titles that ship both builds stay on the default Proton again. Users can still pick FEX for one, and that choice is kept. Linux-only titles still go to FEX.

* fix: unmapped dual-platform games get Proton even when config.vdf cannot be read
2026-10-18 08:59:54 -04:00

89 lines
2.8 KiB
C

#include <dlfcn.h>
#include <stddef.h>
#include <string.h>
#ifndef VULKAN_THUNK
#define VULKAN_THUNK "/usr/lib/x86_64-linux-gnu/libvulkan.so.1"
#endif
#define VK_INCOMPLETE 5
#define VK_ERROR_INITIALIZATION_FAILED (-3)
#define EXTENSIONS 128
typedef int VkResult;
typedef void (*Function)(void);
typedef struct {
char extensionName[256];
unsigned int specVersion;
} VkExtensionProperties;
typedef VkResult (*Enumerate)(const char *, unsigned int *, VkExtensionProperties *);
typedef Function (*ProcAddr)(void *, const char *);
/* The driver cannot enumerate devices for an instance with VK_KHR_display, and gamescope's WSI layer aborts on that. */
static const char *const hidden_extensions[] = {
"VK_KHR_display",
"VK_KHR_get_display_properties2",
"VK_EXT_direct_mode_display",
"VK_EXT_acquire_xlib_display",
"VK_EXT_acquire_drm_display",
"VK_EXT_display_surface_counter",
};
static void *thunk(const char *name)
{
void *library = dlopen(VULKAN_THUNK, RTLD_NOW | RTLD_NOLOAD);
void *found = library ? dlsym(library, name) : NULL;
if (library)
dlclose(library);
return found;
}
static int hidden(const char *name)
{
for (size_t i = 0; i < sizeof(hidden_extensions) / sizeof(hidden_extensions[0]); i++)
if (!strcmp(name, hidden_extensions[i]))
return 1;
return 0;
}
__attribute__((visibility("default"))) VkResult vkEnumerateInstanceExtensionProperties(const char *layer, unsigned int *count,
VkExtensionProperties *properties)
{
static Enumerate real;
VkExtensionProperties all[EXTENSIONS];
unsigned int total = EXTENSIONS, kept = 0, room = properties ? *count : 0;
VkResult result;
if (!real)
real = (Enumerate)thunk("vkEnumerateInstanceExtensionProperties");
if (!real)
return VK_ERROR_INITIALIZATION_FAILED;
result = real(layer, &total, all);
if (result < 0)
return result;
for (unsigned int i = 0; i < total; i++) {
if (hidden(all[i].extensionName))
continue;
if (properties && kept < room)
memcpy(&properties[kept], &all[i], sizeof(all[i]));
kept++;
}
if (!properties) {
*count = kept;
return result;
}
*count = kept < room ? kept : room;
return kept > room ? VK_INCOMPLETE : result;
}
__attribute__((visibility("default"))) Function vkGetInstanceProcAddr(void *instance, const char *name)
{
static ProcAddr real;
if (!strcmp(name, "vkGetInstanceProcAddr"))
return (Function)vkGetInstanceProcAddr;
if (!strcmp(name, "vkEnumerateInstanceExtensionProperties"))
return (Function)vkEnumerateInstanceExtensionProperties;
if (!real)
real = (ProcAddr)thunk("vkGetInstanceProcAddr");
return real ? real(instance, name) : NULL;
}