forked from unom/punktfunk
A wired DualSense on Fedora 44 / Bazzite / Arch presents exactly one playback
sink: the 1-channel `…Default__Speaker__sink`. GE-Proton mints its synthetic
"Sony controller speaker" endpoint from that lone mono sink, and Marvel's
Spider-Man Remastered overruns it — reliably, ~74 s in:
73.846 render_GetBuffer (…)->(5034, …) <- GE's mono endpoint
73.846 EXCEPTION_ACCESS_VIOLATION info[0]=1 (WRITE) info[1]=5CB9A000
Not a format mismatch: `GetMixFormat` and the game's `Initialize` both agree on
mono float32 `nBlockAlign 4`, and pulse sized `maxlength: 20136` = 5034 x 4
correctly. At the fault `rsi=rbp=0x13aa` (5034, the frame count) while
`rcx`/`rdx` are 5206/5207 — the copy loop had already run past the count. It is
a game/GE bug on a code path that ONLY EXISTS WHEN THE MONO SINK DOES.
So delete the mono sink rather than chase the overrun. `alsa-ucm-conf` describes
the pad as Speaker / Headphones / Mic / Headset and has never carried a
`SpeakerHaptic` device — the DualSense profile arrived upstream in 1.2.15
(36a111a) already without it, and the Deck's is a Valve downstream patch they
still carry on their own 1.2.16.1. With `SpeakerHaptic` at `PlaybackPriority
200` against `Speaker`'s 100 the card takes `HiFi (Mic, SpeakerHaptic)`, the
sink is the 4-channel one, and the mono sink — with the crash path — never
exists. The voice coils reach their own channels as a bonus.
Shipped WITHOUT replacing a file `alsa-ucm-conf` owns, which is what made this
awkward to package. `USB-Audio/USB-Audio.conf` ends with an unconditional,
optional include of `USB-Audio/conf.d/{vid}-{pid}.conf`, placed after its device
table has chosen `${var:ProfileName}` and before it includes the profile that
name resolves to — so a two-line drop-in keyed by 054c:0ce6 / 054c:0df2 swaps
the profile with no diversion, no `Conflicts`, and no `%config` fight. Verified
against alsa-lib rather than assumed: `ucm_cond.c` makes `Condition` optional
for a syntax-v8 `If` carrying `Append`, and `uc_mgr_evaluate_include` evaluates
each included subtree in place before moving to the next include, so the
`Define` lands before the profile include substitutes the variable. The hook and
the DualSense profile shipped in the SAME release (1.2.15), so every tree that
has the bug has the hook.
Host packages only (rpm — and therefore the Bazzite sysext, which unpacks the
RPMs — deb, Arch). The client already has a working fallback in
`ensure_pro_audio`, and a shared file in two co-installable packages is a file
conflict for a nicety. NixOS is not covered: it has no /usr/share/alsa/ucm2 to
drop into and needs a package override instead.
`scripts/ci/check-dualsense-ucm.sh` runs the whole chain on a real distro tree
with no hardware, via UCM's card-less `conf.virt.d` path with only the four card
built-ins stubbed. Against pristine Fedora 44 alsa-ucm-conf 1.2.16.1: baseline
`Headphones/Headset/Mic/Speaker`; with the drop-in, `SpeakerHaptic` and
`HeadphonesHaptic` too, `PlaybackPriority/SpeakerHaptic=200` over `Speaker`'s
100, `PlaybackPCM/SpeakerHaptic=…dualsense_haptic_out:…,1,1,2,3`. It exists
because this fix hooks another project's dispatcher: an upstream rename would
neuter it silently, and what comes back is the crash, not a quieter pad.
Negative-tested both ways (typo'd ProfileName, hook deleted).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
104 lines
5.2 KiB
Bash
Executable File
104 lines
5.2 KiB
Bash
Executable File
#!/bin/sh
|
|
# Prove that scripts/alsa-ucm2/ still puts a `SpeakerHaptic` device on the DualSense's card —
|
|
# WITHOUT a DualSense, and without replacing a single file the distro's `alsa-ucm-conf` owns.
|
|
#
|
|
# Why this check exists
|
|
# --------------------
|
|
# The fix it guards is a hook into ANOTHER project's config tree, so it can rot silently:
|
|
# `USB-Audio/USB-Audio.conf` includes `USB-Audio/conf.d/{vid}-{pid}.conf` after its device table
|
|
# has chosen `ProfileName` and before it includes that profile, and our drop-in redefines the
|
|
# variable in between. Rename the profile, drop the include, reorder the two, and our files stop
|
|
# doing anything at all — with no error anywhere. What comes back is not a silent downgrade but
|
|
# the crash: with no `SpeakerHaptic` the card's only playback route is the 1-channel `Speaker`
|
|
# split, PipeWire offers exactly one sink, GE-Proton mints its synthetic "Sony controller
|
|
# speaker" endpoint from it, and a game that opens that endpoint overruns it
|
|
# (EXCEPTION_ACCESS_VIOLATION, reliably ~74 s into Marvel's Spider-Man Remastered).
|
|
#
|
|
# How it runs without hardware
|
|
# ----------------------------
|
|
# UCM's card-less path (`conf.virt.d/${OpenName}.conf`) can drive the whole include chain; the
|
|
# only things missing are the four built-ins a real card publishes. So the harness copies the
|
|
# distro tree to a scratch dir and substitutes literals for exactly those four — the USB id the
|
|
# dispatcher matches on and the three cosmetic name/id strings — and changes NOTHING else. The
|
|
# include ordering, the profile resolution and the priorities under test are the real ones.
|
|
#
|
|
# Skips (exit 0) where it cannot run: no `alsaucm`, or no distro ucm2 tree with the DualSense
|
|
# profile in it. Meant for the Fedora RPM builder image; harmless anywhere else.
|
|
set -eu
|
|
|
|
REPO="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
OVERLAY="$REPO/scripts/alsa-ucm2"
|
|
UCM2="${UCM2_DIR:-/usr/share/alsa/ucm2}"
|
|
# The pad the profile is keyed to; the Edge (0df2) resolves through the same profile.
|
|
USBID="USB054c:0ce6"
|
|
|
|
[ -d "$OVERLAY/USB-Audio" ] || { echo "check-dualsense-ucm: no $OVERLAY — wrong repo?" >&2; exit 1; }
|
|
|
|
if ! command -v alsaucm >/dev/null 2>&1; then
|
|
echo "check-dualsense-ucm: no alsaucm on PATH (Fedora: dnf install alsa-ucm-utils) — skipped"
|
|
exit 0
|
|
fi
|
|
if [ ! -f "$UCM2/USB-Audio/Sony/DualSense-PS5.conf" ]; then
|
|
echo "check-dualsense-ucm: $UCM2 has no DualSense profile (Fedora: dnf install alsa-ucm) — skipped"
|
|
exit 0
|
|
fi
|
|
|
|
TREE="$(mktemp -d)"
|
|
trap 'rm -rf "$TREE"' EXIT INT TERM
|
|
cp -a "$UCM2/." "$TREE/"
|
|
|
|
# Feed the dispatcher the pad's USB id, which normally comes off the card.
|
|
sed -i.bak \
|
|
-e "s|String \"\${CardComponents}\"|String \"$USBID\"|" \
|
|
-e "s|Haystack \"\${CardComponents}\"|Haystack \"$USBID\"|" \
|
|
"$TREE/USB-Audio/USB-Audio.conf"
|
|
# ...and the three name/id strings the device sections interpolate. Cosmetic to this test: they
|
|
# only ever land in a Comment or in the `hw:` PCM name, never in a priority or a channel map.
|
|
find "$TREE/USB-Audio" "$TREE/common" -name '*.conf' -exec sed -i.bak \
|
|
-e 's|${CardName}|DualSense|g' \
|
|
-e 's|${CardId}|Controller|g' \
|
|
-e 's|${CardLongName}|DualSenseLong|g' {} +
|
|
find "$TREE" -name '*.conf.bak' -delete
|
|
|
|
mkdir -p "$TREE/conf.virt.d"
|
|
printf 'Syntax 8\nInclude.a.File "/USB-Audio/USB-Audio.conf"\n' > "$TREE/conf.virt.d/pftest.conf"
|
|
|
|
ucm() { ALSA_CONFIG_UCM2="$TREE" alsaucm -c pftest "$@" 2>&1 | grep -v 'no soundcards found' || true; }
|
|
|
|
# Baseline: informational only. Upstream adopting the device would make our drop-in redundant
|
|
# rather than wrong, and that is worth seeing in the log rather than failing on.
|
|
before="$(ucm set _verb Default list _devices | grep -c 'SpeakerHaptic' || true)"
|
|
|
|
cp -a "$OVERLAY/USB-Audio/." "$TREE/USB-Audio/"
|
|
find "$TREE/USB-Audio/Punktfunk" -name '*.conf' -exec sed -i.bak -e 's|${CardId}|Controller|g' {} +
|
|
find "$TREE" -name '*.conf.bak' -delete
|
|
|
|
after="$(ucm set _verb Default list _devices || true)"
|
|
echo "$after" | grep -q 'SpeakerHaptic' || {
|
|
echo "check-dualsense-ucm: FAIL — the drop-in did not add a SpeakerHaptic device." >&2
|
|
echo " The distro's $UCM2/USB-Audio/USB-Audio.conf probably no longer resolves the" >&2
|
|
echo " DualSense through \${var:ProfileName}, or no longer includes USB-Audio/conf.d/." >&2
|
|
echo " Devices seen:" >&2
|
|
echo "$after" | sed 's/^/ /' >&2
|
|
exit 1
|
|
}
|
|
|
|
# Existing is not enough: it is OUTRANKING the mono `Speaker` that keeps the 1-channel sink —
|
|
# and the crash path with it — from ever being minted.
|
|
prios="$(ucm set _verb Default get PlaybackPriority/SpeakerHaptic get PlaybackPriority/Speaker)"
|
|
haptic="$(echo "$prios" | sed -n 's|.*PlaybackPriority/SpeakerHaptic=\([0-9]*\).*|\1|p')"
|
|
mono="$(echo "$prios" | sed -n 's|.*PlaybackPriority/Speaker=\([0-9]*\).*|\1|p')"
|
|
[ -n "$haptic" ] && [ -n "$mono" ] || {
|
|
echo "check-dualsense-ucm: FAIL — could not read both playback priorities:" >&2
|
|
echo "$prios" | sed 's/^/ /' >&2
|
|
exit 1
|
|
}
|
|
[ "$haptic" -gt "$mono" ] || {
|
|
echo "check-dualsense-ucm: FAIL — SpeakerHaptic ($haptic) does not outrank Speaker ($mono)," >&2
|
|
echo " so the card can still land on the 1-channel sink that games overrun." >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "check-dualsense-ucm: ok — SpeakerHaptic present at priority $haptic over Speaker's $mono" \
|
|
"(distro tree alone had $before)"
|