mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
* 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
142 lines
5.8 KiB
Python
142 lines
5.8 KiB
Python
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
SOURCE = Path(__file__).resolve().parents[1] / "linuxfs/fex/vulkanpin.c"
|
|
THUNK = r"""
|
|
#include <string.h>
|
|
typedef struct { char extensionName[256]; unsigned int specVersion; } VkExtensionProperties;
|
|
typedef void (*Function)(void);
|
|
static const char *names[] = {"VK_KHR_surface", "VK_KHR_display", "VK_KHR_xlib_surface", "VK_EXT_direct_mode_display",
|
|
"VK_KHR_get_display_properties2", "VK_EXT_swapchain_colorspace"};
|
|
|
|
int vkEnumerateInstanceExtensionProperties(const char *layer, unsigned int *count, VkExtensionProperties *properties)
|
|
{
|
|
unsigned int total = layer ? 0 : sizeof(names) / sizeof(names[0]);
|
|
if (!properties) {
|
|
*count = total;
|
|
return 0;
|
|
}
|
|
unsigned int n = *count < total ? *count : total;
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
memset(&properties[i], 0, sizeof(properties[i]));
|
|
strcpy(properties[i].extensionName, names[i]);
|
|
properties[i].specVersion = i + 1;
|
|
}
|
|
*count = n;
|
|
return n < total ? 5 : 0;
|
|
}
|
|
|
|
int vkCreateInstance(void) { return 42; }
|
|
|
|
Function vkGetInstanceProcAddr(void *instance, const char *name)
|
|
{
|
|
(void)instance;
|
|
if (!strcmp(name, "vkEnumerateInstanceExtensionProperties"))
|
|
return (Function)vkEnumerateInstanceExtensionProperties;
|
|
if (!strcmp(name, "vkCreateInstance"))
|
|
return (Function)vkCreateInstance;
|
|
if (!strcmp(name, "vkGetInstanceProcAddr"))
|
|
return (Function)vkGetInstanceProcAddr;
|
|
return 0;
|
|
}
|
|
"""
|
|
GAME = r"""
|
|
#include <dlfcn.h>
|
|
#include <stdio.h>
|
|
typedef struct { char extensionName[256]; unsigned int specVersion; } VkExtensionProperties;
|
|
typedef void (*Function)(void);
|
|
typedef int (*Enumerate)(const char *, unsigned int *, VkExtensionProperties *);
|
|
typedef Function (*ProcAddr)(void *, const char *);
|
|
#ifdef LINKED
|
|
int vkCreateInstance(void);
|
|
Function vkGetInstanceProcAddr(void *, const char *);
|
|
#endif
|
|
|
|
static void list(Enumerate enumerate)
|
|
{
|
|
VkExtensionProperties found[8];
|
|
unsigned int count = 0;
|
|
int result = enumerate(0, &count, 0);
|
|
printf("count %u %d\n", count, result);
|
|
enumerate(0, &count, found);
|
|
for (unsigned int i = 0; i < count; i++)
|
|
printf("%s %u\n", found[i].extensionName, found[i].specVersion);
|
|
count = 1;
|
|
result = enumerate(0, &count, found);
|
|
printf("short %u %d %s\n", count, result, found[0].extensionName);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
#ifdef LINKED
|
|
ProcAddr proc = vkGetInstanceProcAddr;
|
|
printf("linked %d\n", vkCreateInstance());
|
|
#else
|
|
void *vulkan = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
|
|
if (!vulkan)
|
|
return 1;
|
|
ProcAddr proc = (ProcAddr)dlsym(vulkan, "vkGetInstanceProcAddr");
|
|
printf("same handle %d\n", dlopen("libvulkan.so", RTLD_NOW | RTLD_NOLOAD) == vulkan);
|
|
printf("create %d\n", ((int (*)(void))dlsym(vulkan, "vkCreateInstance"))());
|
|
list((Enumerate)dlsym(vulkan, "vkEnumerateInstanceExtensionProperties"));
|
|
#endif
|
|
printf("same proc %d create %d missing %d\n", proc(0, "vkGetInstanceProcAddr") == (Function)proc,
|
|
((int (*)(void))proc(0, "vkCreateInstance"))(), proc(0, "vkNothing") == 0);
|
|
list((Enumerate)proc(0, "vkEnumerateInstanceExtensionProperties"));
|
|
return 0;
|
|
}
|
|
"""
|
|
LISTED = """count 3 0
|
|
VK_KHR_surface 1
|
|
VK_KHR_xlib_surface 3
|
|
VK_EXT_swapchain_colorspace 6
|
|
short 1 5 VK_KHR_surface
|
|
"""
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("gcc") and os.uname().machine == "x86_64", "needs gcc on an x86-64 host")
|
|
class VulkanPinTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = Path(tempfile.mkdtemp())
|
|
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
|
|
|
def gcc(self, *args):
|
|
made = subprocess.run(["gcc"] + [str(a) for a in args], capture_output=True, text=True)
|
|
self.assertEqual(made.returncode, 0, made.stderr)
|
|
|
|
def test_display_extensions_are_hidden_in_front_of_the_thunk(self):
|
|
thunk = self.tmp / "usr/lib/libvulkan.so.1"
|
|
thunk.parent.mkdir(parents=True)
|
|
(self.tmp / "thunk.c").write_text(THUNK)
|
|
self.gcc("-shared", "-fPIC", "-Wl,-soname,libvulkan.so.1", "-o", thunk, self.tmp / "thunk.c")
|
|
stub = self.tmp / "stub/libvulkan.so.1"
|
|
stub.parent.mkdir()
|
|
self.gcc("-shared", "-nostdlib", "-Wl,-soname," + str(thunk), "-o", stub, "-x", "c", "/dev/null")
|
|
pinned = self.tmp / "pin"
|
|
pinned.mkdir()
|
|
self.gcc("-shared", "-fPIC", "-O2", "-Wall", "-Wextra", "-Werror", "-fvisibility=hidden", '-DVULKAN_THUNK="%s"' % thunk,
|
|
"-Wl,-soname,libvulkan.so.1", "-Wl,-z,nodelete", "-Wl,--no-as-needed", "-o", pinned / "libvulkan.so.1",
|
|
SOURCE, stub, "-ldl")
|
|
os.symlink("libvulkan.so.1", pinned / "libvulkan.so")
|
|
exported = subprocess.run(["nm", "-D", "--defined-only", pinned / "libvulkan.so.1"], capture_output=True, text=True)
|
|
self.assertEqual(sorted(line.split()[-1] for line in exported.stdout.splitlines()),
|
|
["vkEnumerateInstanceExtensionProperties", "vkGetInstanceProcAddr"])
|
|
(self.tmp / "game.c").write_text(GAME)
|
|
env = dict(os.environ, LD_LIBRARY_PATH=str(pinned))
|
|
self.gcc("-o", self.tmp / "loader", self.tmp / "game.c", "-ldl")
|
|
run = subprocess.run([self.tmp / "loader"], capture_output=True, text=True, env=env)
|
|
self.assertEqual(run.returncode, 0, run.stderr)
|
|
self.assertEqual(run.stdout, "same handle 1\ncreate 42\n" + LISTED + "same proc 1 create 42 missing 1\n" + LISTED)
|
|
self.gcc("-DLINKED", "-o", self.tmp / "linked", self.tmp / "game.c", "-L", thunk.parent, "-l:libvulkan.so.1", "-ldl")
|
|
run = subprocess.run([self.tmp / "linked"], capture_output=True, text=True, env=env)
|
|
self.assertEqual(run.returncode, 0, run.stderr)
|
|
self.assertEqual(run.stdout, "linked 42\nsame proc 1 create 42 missing 1\n" + LISTED)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|