Force SSBS for Proton games (libssbs.so), switch under FEX preset (#328)

* Force SSBS for a game's Wine: libssbs.so, opt in per game

Wine's arm64 signal code resumes threads from a Windows CONTEXT whose
Cpsr never carries PSTATE.SSBS, so every signal return leaves the
thread running with speculative store bypass disabled - slower on cores
such as Oryon. Bannerlator fixes it in its own Wine builds
(force_ssbs.diff, Sonicadvance1) and with an LD_PRELOAD adapter for
layers it cannot rebuild. DroidDeck's Protons (Valve, GE, CachyOS ARM64)
come prebuilt from Steam but run the same aarch64 Wine signal path, so
the adapter applies here: tools/linuxfs/ssbs/ssbs_adapter.c, the same
source built for glibc (+ sysv_signal), staged to
/usr/local/lib/libssbs.so.

droiddeck-game-env preloads it when the game's (or the shared)
environment sets DROIDDECK_FORCE_SSBS=1, and only into an ARM64 Proton.
Off unless asked for: the gain is unproven on Snapdragon 8 Gen 3.

For testing: SSBS_ADAPTER_OBSERVE=1 counts without changing anything
(the baseline) and SSBS_ADAPTER_DEBUG=1 logs the fixes; both also count
signals that ARRIVE while a thread runs without SSBS, which shows
whether anything besides signal returns clears it. Checked on an ARM64
Linux kernel: a handler that clears SSBS left 4 of 5 later signals
arriving with it clear; with the adapter, 0 of 5.

* Force SSBS: a switch under the FEX preset in Steam settings

Steam settings > Games gets "Force SSBS" right under the FEX preset, off
unless turned on. Like the preset it goes into the game environment's
defaults (DROIDDECK_FORCE_SSBS=1), published at once and read by
droiddeck-game-env on the next game launch, so a game's own environment
can still set it to 0.

Measured on the device with the adapter's counters (AYANEO Pocket FIT,
DiRT 3 on GE-Proton11-7-aarch64): observing only, 8192 of 8277 signals
found the thread running without SSBS; with the adapter, none did - it
keeps the bit set everywhere Wine clears it, as a rebuilt ntdll would.
The speed-up itself is reported on Oryon cores and was not measurable on
the FIT's 8 Gen 3, hence off by default.

---------

Co-authored-by: The412Banner <[email protected]>
This commit is contained in:
The412BannerandThe412Banner authored and GitHub committed 2026-10-06 09:27:15 -04:00
1 parent a1b01a04a8
commit bc01b77752
13 files changed
+283 -6

No files matched your search

+9 -1
View File
@@ -173,7 +173,7 @@ jobs:
uses: actions/cache@v4
with:
path: app/src/main/assets/linuxfs
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*') }}
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*') }}
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
if: steps.preloads.outputs.cache-hit != 'true'
@@ -195,6 +195,10 @@ jobs:
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$D/libblfastpath.so" tools/proot/fastpath/fastpath.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libblfastpath.so"
# Force-SSBS adapter, preloaded into a game's Wine only when the game asks for it.
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so"
mkdir -p "$D/usr/local/bin"
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
@@ -362,6 +366,10 @@ jobs:
for sym in socket bind getsockname setsockopt statfs statvfs syscall shm_open shm_unlink mmap mmap64 munmap drmPrimeFDToHandle drmCloseBufferHandle drmIoctl; do
grep -qx "$sym" <<<"$SESSION" || { echo "::error::libblsession.so does not export $sym"; exit 1; }
done
SSBS=$(syms "$D/libssbs.so")
for sym in sigaction signal bsd_signal sysv_signal; do
grep -qx "$sym" <<<"$SSBS" || { echo "::error::libssbs.so does not export $sym"; exit 1; }
done
test -f "$D/usr/local/bin/droiddeck-session"
test -f "$D/usr/local/bin/droiddeck-proton-extra"
@@ -311,6 +311,7 @@ class MainActivity : ComponentActivity() {
private var settingsMode by mutableStateOf<String?>(null)
private var resolution by mutableStateOf(com.droiddeck.launcher.session.SessionDisplay.DEFAULT_RESOLUTION)
private var fexPreset by mutableStateOf("")
private var forceSsbs by mutableStateOf(false)
private var steamChannel by mutableStateOf("steamdeck_publicbeta")
private var runSteamAtStartup by mutableStateOf(false)
private var theme by mutableStateOf("graphite")
@@ -1159,6 +1160,7 @@ class MainActivity : ComponentActivity() {
storageOptions = storageOptions,
storageDiagnostics = mode == SessionService.MODE_STEAM && storageDiagnostics,
fexPreset = if (mode == SessionService.MODE_STEAM) fexPreset else null,
forceSsbs = forceSsbs,
syncBackend = if (mode == SessionService.MODE_STEAM) SessionPrefs.syncBackendOf(fastSync, fsyncFirst, syncFallback) else null,
steamChannel = if (mode == SessionService.MODE_STEAM) steamChannel else null,
steamDeckMode = mode == SessionService.MODE_STEAM && steamDeckMode,
@@ -1232,6 +1234,7 @@ class MainActivity : ComponentActivity() {
storageDiagnostics = on
},
onFexPreset = { id -> SessionPrefs.setFexPreset(this, id); fexPreset = id },
onForceSsbs = { on -> SessionPrefs.setForceSsbs(this, on); forceSsbs = on },
onSyncBackend = { id ->
SessionPrefs.setSyncBackend(this, id)
fastSync = SessionPrefs.fastSync(this)
@@ -1353,6 +1356,7 @@ class MainActivity : ComponentActivity() {
showMapping = false
resolution = SessionPrefs.resolutionChoice(this, mode, com.droiddeck.launcher.session.SessionDisplay.panelSize(this))
fexPreset = SessionPrefs.fexPreset(this)
forceSsbs = SessionPrefs.forceSsbs(this)
fastSync = SessionPrefs.fastSync(this)
fsyncFirst = SessionPrefs.fsyncFirst(this)
syncFallback = SessionPrefs.syncFallback(this)
@@ -25,7 +25,7 @@ object GameEnvironment {
fun validValue(value: String) = '\u0000' !in value && value.length <= 8192
fun validScope(scope: String) = scope.isEmpty() || (Regex("[1-9][0-9]*").matches(scope) && scope.toLongOrNull()?.let { it in 1..4294967295L } == true)
fun defaults(preset: String): Map<String, String?> = linkedMapOf<String, String?>(
fun defaults(preset: String, forceSsbs: Boolean = false): Map<String, String?> = linkedMapOf<String, String?>(
"MESA_SHADER_CACHE_DISABLE" to "false",
"VKD3D_FEATURE_LEVEL" to "12_2",
"VKD3D_SHADER_MODEL" to "6_9",
@@ -37,8 +37,12 @@ object GameEnvironment {
"DOTNET_EnableWriteXorExecute" to "0",
).apply {
FexPreset.env(preset).forEach { put(it.substringBefore('='), it.substringAfter('=')) }
// Steam settings' Force SSBS: droiddeck-game-env preloads libssbs.so into the game's Wine.
if (forceSsbs) put(FORCE_SSBS, "1")
}
fun effective(config: Config, preset: String, scope: String): Map<String, String?> =
defaults(preset) + config.shared + if (scope.isEmpty()) emptyMap() else config.games[scope].orEmpty()
const val FORCE_SSBS = "DROIDDECK_FORCE_SSBS"
fun effective(config: Config, preset: String, scope: String, forceSsbs: Boolean = false): Map<String, String?> =
defaults(preset, forceSsbs) + config.shared + if (scope.isEmpty()) emptyMap() else config.games[scope].orEmpty()
}
@@ -59,7 +59,7 @@ object GameEnvironmentStore {
@Synchronized
fun publish(context: Context, config: GameEnvironment.Config = read(context)) {
val resolved = config.copy(shared = GameEnvironment.defaults(SessionPrefs.fexPreset(context)) + config.shared)
val resolved = config.copy(shared = GameEnvironment.defaults(SessionPrefs.fexPreset(context), SessionPrefs.forceSsbs(context)) + config.shared)
val json = encode(resolved)
// The Display page's texture filtering, as DXVK options the launcher appends to
// DXVK_CONFIG after the profiles - a user's own DXVK_CONFIG entry keeps its options.
@@ -36,6 +36,7 @@ object SessionFiles {
"libblsession.so" to "usr/local/lib/libblsession.so",
"libfakeinput.so" to "usr/local/lib/libfakeinput.so",
"libblfastpath.so" to "usr/local/lib/libblfastpath.so",
"libssbs.so" to "usr/local/lib/libssbs.so",
"usr/local/bin/droiddeck-session" to "usr/local/bin/droiddeck-session",
"usr/local/bin/steam-compatibility" to "usr/local/bin/steam-compatibility",
"usr/local/bin/droiddeck-clipboard" to "usr/local/bin/droiddeck-clipboard",
@@ -543,6 +543,22 @@ object SessionPrefs {
private const val DEFAULT_FEX_PRESET = "PERFORMANCE_TSO"
/**
* Force SSBS for Proton games: Wine resumes threads from a Windows CONTEXT that never carries
* PSTATE.SSBS, so they run with speculative store bypass disabled; libssbs.so keeps it set
* (on DiRT 3 / GE-Proton: from ~99% of a game's threads running without it to none). The
* speed-up is reported on Oryon cores (Snapdragon 8 Elite) and was not measurable on an
* 8 Gen 3, so it is off unless turned on. A game's own environment can still say
* DROIDDECK_FORCE_SSBS=0.
*/
fun forceSsbs(context: Context): Boolean = prefs(context).getBoolean("forceSsbs", false)
fun setForceSsbs(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("forceSsbs", on).apply()
runCatching { GameEnvironmentStore.publish(context) }
.onFailure { android.util.Log.e("GameEnvironment", "Could not update game environment", it) }
}
fun setFexPreset(context: Context, id: String) {
prefs(context).edit().putString("fexPreset", id).apply()
runCatching { GameEnvironmentStore.publish(context) }
@@ -95,6 +95,7 @@ private fun GameEnvironmentEditor(byPad: Boolean, onClose: () -> Unit) {
var error by remember { mutableStateOf(false) }
var editing by remember { mutableStateOf<Pair<String, String>?>(null) }
val preset = SessionPrefs.fexPreset(context)
val forceSsbs = SessionPrefs.forceSsbs(context)
val firstFocus = remember { FocusRequester() }
var editByPad by remember { mutableStateOf(false) }
val dialogInput = LocalInputModeManager.current
@@ -159,7 +160,7 @@ private fun GameEnvironmentEditor(byPad: Boolean, onClose: () -> Unit) {
if (current == null && !error) Small(stringResource(R.string.game_env_loading))
if (current != null) {
val own = current.entries(scope)
val entries = GameEnvironment.effective(current, preset, scope).toSortedMap()
val entries = GameEnvironment.effective(current, preset, scope, forceSsbs).toSortedMap()
if (entries.isEmpty()) Small(stringResource(R.string.game_env_empty))
else Panel {
entries.entries.forEachIndexed { i, (name, value) ->
@@ -70,6 +70,8 @@ class ModeSettings(
/** Steam only: record allocation and sampled storage timings in the next session's Share logs. */
val storageDiagnostics: Boolean = false,
val fexPreset: String? = null,
/** Steam only: Force SSBS for Proton games (SessionPrefs.forceSsbs). */
val forceSsbs: Boolean = false,
/** Steam only: SessionPrefs.SYNC_* chosen for Proton games; null outside Steam. */
val syncBackend: String? = null,
/** Steam only: forces game windows fullscreen (null = not a Steam page). */
@@ -131,6 +133,7 @@ class ModeSettingsActions(
val onPickGameStorageFolder: () -> Unit = {},
val onStorageDiagnostics: (Boolean) -> Unit = {},
val onFexPreset: (String) -> Unit = {},
val onForceSsbs: (Boolean) -> Unit = {},
val onSyncBackend: (String) -> Unit = {},
val onForceFullscreen: (Boolean) -> Unit = {},
val onStretch16x9: (Boolean) -> Unit = {},
@@ -459,6 +462,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
FexPreset.all.map { it.id to stringResource(it.label) }, s.fexPreset,
note = stringResource(FexPreset.byId(s.fexPreset).detail), onPick = a.onFexPreset,
)
ToggleRow(host, "ssbs", stringResource(R.string.force_ssbs_title), stringResource(R.string.force_ssbs_hint), s.forceSsbs, onChange = a.onForceSsbs)
GameEnvironmentRow()
}
+2
View File
@@ -1052,4 +1052,6 @@
<string name="device_info_copy">Copy</string>
<string name="device_info_copy_hint">Copy these details into a bug report.</string>
<string name="device_info_copied">Copied.</string>
<string name="force_ssbs_title">Force SSBS</string>
<string name="force_ssbs_hint">Keeps a CPU speed setting on that Wine keeps turning off. Can make Windows games faster on Snapdragon 8 Elite; little change seen on older chips. Applies on the next game launch.</string>
</resources>
@@ -11,6 +11,14 @@ class GameEnvironmentTest {
assertEquals("6_9", values["VKD3D_SHADER_MODEL"])
}
@Test fun forceSsbsIsADefaultAGameCanTurnOff() {
assertFalse(GameEnvironment.defaults("").containsKey(GameEnvironment.FORCE_SSBS))
assertEquals("1", GameEnvironment.defaults("", forceSsbs = true)[GameEnvironment.FORCE_SSBS])
val config = GameEnvironment.Config().withEntries("42", mapOf(GameEnvironment.FORCE_SSBS to "0"))
assertEquals("0", GameEnvironment.effective(config, "", "42", forceSsbs = true)[GameEnvironment.FORCE_SSBS])
assertEquals("1", GameEnvironment.effective(config, "", "43", forceSsbs = true)[GameEnvironment.FORCE_SSBS])
}
@Test fun gameOverridesSharedAndPresetWithoutLeakingToOtherGames() {
val config = GameEnvironment.Config(
shared = mapOf("FEX_MULTIBLOCK" to "0", "VKD3D_FEATURE_LEVEL" to "12_2"),
@@ -196,6 +196,30 @@ def without_pack(chosen_env, env):
return result
SSBS_LIB = "/usr/local/lib/libssbs.so"
def force_ssbs(command, env):
"""DROIDDECK_FORCE_SSBS=1 (a game's or the shared environment): preload libssbs.so into this
Proton's Wine, which then keeps PSTATE.SSBS set across signal returns - speculative store
bypass stays allowed, as force_ssbs.diff does inside a rebuilt ntdll. ARM64 Protons only: the
library is aarch64 and only arm64 Wine has the signal path it fixes."""
if env.get("DROIDDECK_FORCE_SSBS", "").lower() not in ("1", "on", "true", "yes"):
return env
proton = os.path.dirname(command[0]) if command else ""
if not os.path.isdir(os.path.join(proton, "files/lib/wine/aarch64-unix")):
print("droiddeck-game-env: force SSBS asked for, but this Proton is not an ARM64 one; left off", file=sys.stderr)
return env
if not os.path.isfile(SSBS_LIB):
print("droiddeck-game-env: force SSBS asked for, but %s is missing; left off" % SSBS_LIB, file=sys.stderr)
return env
parts = [p for p in re.split(r"[:\s]+", env.get("LD_PRELOAD", "")) if p]
if SSBS_LIB not in parts:
env = dict(env, LD_PRELOAD=":".join(parts + [SSBS_LIB]))
print("droiddeck-game-env: force SSBS on (libssbs.so preloaded)", file=sys.stderr)
return env
def main():
command = sys.argv[1:]
if not command:
@@ -223,6 +247,7 @@ def main():
env = apply_config(env, {"version": 1}, app_id, fixes)
except (OSError, ValueError, TypeError, AttributeError):
print("droiddeck-game-env: invalid configuration; using inherited environment", file=sys.stderr)
env = force_ssbs(command, env)
sync = load_sync()
launch, launch_env = select_sync(command, env, sync)
if launch[0] != command[0]:
+180
View File
@@ -0,0 +1,180 @@
/*
* libssbs.so - keep PSTATE.SSBS set across signal returns, without rebuilding Wine.
*
* Wine's arm64 signal code writes PSTATE back from a Windows CONTEXT, whose Cpsr never carries
* SSBS (bit 12). Every signal return through that code (exception fixups, NtSetContextThread on
* another thread, the SIGUSR2 suspend path) therefore resumes the thread with SSBS clear, so the
* CPU runs it with speculative store bypass disabled - slower on cores such as Oryon.
* force_ssbs.diff (Sonicadvance1, FEX) fixes this inside ntdll; this library does the same from
* outside, for Protons that cannot be rebuilt: the Valve, GE and CachyOS ARM64 builds DroidDeck
* runs, which come prebuilt from Steam. Same source as Bannerlator's adapter (bionic), built here
* for glibc.
*
* How: interpose sigaction()/signal() via LD_PRELOAD. Every handler is installed behind one
* SA_SIGINFO trampoline that calls the real handler and then sets SSBS in the ucontext it is
* about to return through. The kernel accepts SSBS from userspace on sigreturn and context-
* switches it per thread, so the bit sticks until the next sigreturn. Handlers that never return
* (siglongjmp) are unaffected either way.
*
* Env:
* SSBS_ADAPTER_DISABLE=1 pass everything through untouched.
* SSBS_ADAPTER_OBSERVE=1 wrap and count, but leave SSBS as the handler left it (the baseline).
* SSBS_ADAPTER_DEBUG=1 log to stderr (implied by OBSERVE):
* - "returned with SSBS clear": a handler was about to resume a thread without SSBS.
* - "arrived with SSBS clear": a thread was RUNNING without SSBS when a signal reached it.
* Under the adapter this should stop after each thread's first fixed return; if it keeps
* counting, something other than a signal return is clearing the bit.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#define PSTATE_SSBS 0x1000ULL
#define MAX_SIG 65
typedef void (*sigact_fn)(int, siginfo_t *, void *);
typedef void (*sighand_fn)(int);
typedef int (*sigaction_fn)(int, const struct sigaction *, struct sigaction *);
/* Per signal: the handler the program asked for, and whether it wanted SA_SIGINFO. */
static _Atomic(void *) real_handler[MAX_SIG];
static atomic_bool real_siginfo[MAX_SIG];
static sigaction_fn next_sigaction;
static int disabled;
static int observe;
static int debug;
static atomic_ulong n_returns, n_fixed, n_arrived, n_arrived_clear;
static void init(void)
{
if (next_sigaction) return;
next_sigaction = (sigaction_fn)dlsym(RTLD_NEXT, "sigaction");
const char *e = getenv("SSBS_ADAPTER_DISABLE");
disabled = e && *e == '1';
e = getenv("SSBS_ADAPTER_OBSERVE");
observe = e && *e == '1';
e = getenv("SSBS_ADAPTER_DEBUG");
debug = observe || (e && *e == '1');
}
__attribute__((constructor)) static void ctor(void)
{
init();
if (debug) fprintf(stderr, "libssbs: loaded (%s)\n", disabled ? "disabled" : observe ? "observing only" : "active");
}
/* 1, 2, 4, 8, ...: enough lines to see a trend without flooding the log. */
static int milestone(unsigned long n) { return (n & (n - 1)) == 0; }
static void trampoline(int sig, siginfo_t *info, void *uc_v)
{
ucontext_t *uc = uc_v;
if (debug) {
unsigned long a = atomic_fetch_add(&n_arrived, 1) + 1;
if (!(uc->uc_mcontext.pstate & PSTATE_SSBS)) {
unsigned long c = atomic_fetch_add(&n_arrived_clear, 1) + 1;
if (milestone(c))
fprintf(stderr, "libssbs: sig %d arrived with SSBS clear (%lu of %lu arrivals)\n", sig, c, a);
}
}
void *h = atomic_load_explicit(&real_handler[sig], memory_order_acquire);
if (h) {
if (atomic_load_explicit(&real_siginfo[sig], memory_order_relaxed))
((sigact_fn)h)(sig, info, uc_v);
else
((sighand_fn)h)(sig);
}
if (debug) {
unsigned long r = atomic_fetch_add(&n_returns, 1) + 1;
if (!(uc->uc_mcontext.pstate & PSTATE_SSBS)) {
unsigned long f = atomic_fetch_add(&n_fixed, 1) + 1;
if (milestone(f))
fprintf(stderr, "libssbs: sig %d returned with SSBS clear, %s (%lu of %lu returns)\n",
sig, observe ? "left as is" : "set it", f, r);
}
}
if (!observe) uc->uc_mcontext.pstate |= PSTATE_SSBS;
}
static int wrapped(void *h)
{
return h != (void *)SIG_DFL && h != (void *)SIG_IGN && h != (void *)SIG_ERR;
}
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact)
{
init();
if (disabled || sig <= 0 || sig >= MAX_SIG)
return next_sigaction(sig, act, oldact);
struct sigaction mine;
const struct sigaction *pass = act;
void *new_h = NULL;
int new_info = 0;
if (act) {
new_info = (act->sa_flags & SA_SIGINFO) != 0;
new_h = new_info ? (void *)act->sa_sigaction : (void *)act->sa_handler;
if (wrapped(new_h)) {
mine = *act;
mine.sa_flags |= SA_SIGINFO;
mine.sa_sigaction = trampoline;
pass = &mine;
}
}
void *prev_h = atomic_load(&real_handler[sig]);
int prev_info = atomic_load(&real_siginfo[sig]);
/* Publish before installing so the trampoline never runs with a stale handler. */
if (act && wrapped(new_h)) {
atomic_store(&real_siginfo[sig], new_info);
atomic_store_explicit(&real_handler[sig], new_h, memory_order_release);
}
int ret = next_sigaction(sig, pass, oldact);
if (ret != 0 && act && wrapped(new_h)) {
atomic_store(&real_siginfo[sig], prev_info);
atomic_store(&real_handler[sig], prev_h);
}
/* Report the caller's own handler, not the trampoline, so handler chaining keeps working. */
if (ret == 0 && oldact && oldact->sa_sigaction == trampoline && prev_h) {
if (prev_info) {
oldact->sa_sigaction = (sigact_fn)prev_h;
} else {
oldact->sa_flags &= ~SA_SIGINFO;
oldact->sa_handler = (sighand_fn)prev_h;
}
}
return ret;
}
sighandler_t signal(int sig, sighandler_t handler)
{
struct sigaction sa, old;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(sig, &sa, &old) != 0) return SIG_ERR;
return (old.sa_flags & SA_SIGINFO) ? (sighandler_t)(void *)old.sa_sigaction : old.sa_handler;
}
sighandler_t bsd_signal(int sig, sighandler_t handler) { return signal(sig, handler); }
/* glibc's System V flavour: a one-shot handler with no restart. */
sighandler_t sysv_signal(int sig, sighandler_t handler)
{
struct sigaction sa, old;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags = SA_RESETHAND | SA_NODEFER;
sigemptyset(&sa.sa_mask);
if (sigaction(sig, &sa, &old) != 0) return SIG_ERR;
return (old.sa_flags & SA_SIGINFO) ? (sighandler_t)(void *)old.sa_sigaction : old.sa_handler;
}
+24
View File
@@ -461,3 +461,27 @@ class DirectAudioPrefixTest(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
class ForceSsbsTest(unittest.TestCase):
def test_libssbs_is_preloaded_only_when_asked_and_only_into_arm64_proton(self):
with tempfile.TemporaryDirectory() as tmp:
arm = Path(tmp) / "arm"
(arm / "files/lib/wine/aarch64-unix").mkdir(parents=True)
x86 = Path(tmp) / "x86"
(x86 / "files/lib/wine/x86_64-unix").mkdir(parents=True)
lib = Path(tmp) / "libssbs.so"
lib.write_bytes(b"\x7fELF")
g = MODULE["force_ssbs"].__globals__
old = g["SSBS_LIB"]
g["SSBS_LIB"] = str(lib)
try:
env = {"LD_PRELOAD": "/usr/local/lib/libblsession.so"}
self.assertEqual(MODULE["force_ssbs"]([str(arm / "proton"), "waitforexitandrun"], env), env)
on = dict(env, DROIDDECK_FORCE_SSBS="1")
got = MODULE["force_ssbs"]([str(arm / "proton"), "waitforexitandrun"], on)
self.assertEqual(got["LD_PRELOAD"], "/usr/local/lib/libblsession.so:" + str(lib))
self.assertEqual(MODULE["force_ssbs"]([str(arm / "proton")], got)["LD_PRELOAD"], got["LD_PRELOAD"])
self.assertEqual(MODULE["force_ssbs"]([str(x86 / "proton")], on), on)
finally:
g["SSBS_LIB"] = old