aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-01 15:31:43 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-01 15:31:43 -0500
commit45aefb63e84fd3c75be7b24264885a53f3fb44b8 (patch)
treeb702079f16be91eb7e170d608c3de9879976c10e
parente92d166ce97a9335f0e16526b40dc5dc456c361c (diff)
Fix SLR download failures and CMake rebuild detection
Replace SHA256SUMS verification with Content-Length check for SLR downloads. Valve's CDN frequently serves stale SHA256SUMS that don't match the current archive, causing persistent checksum mismatches. Fix CMake DEPENDS for nak_ffi to glob all .rs sources instead of hardcoding a few files — previously changes to slr.rs (and most other Rust files) were silently ignored by ninja. Also wrap prefix init and runtime commands through SLR pressure-vessel container, and expose Proton install directories to the container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
-rw-r--r--libs/nak/src/installers/prefix_setup.rs6
-rw-r--r--libs/nak/src/runtime_wrap.rs47
-rw-r--r--libs/nak/src/slr.rs490
-rw-r--r--libs/nak_ffi/CMakeLists.txt9
-rw-r--r--src/src/protonlauncher.cpp13
5 files changed, 293 insertions, 272 deletions
diff --git a/libs/nak/src/installers/prefix_setup.rs b/libs/nak/src/installers/prefix_setup.rs
index 468aaa2..6c48b09 100644
--- a/libs/nak/src/installers/prefix_setup.rs
+++ b/libs/nak/src/installers/prefix_setup.rs
@@ -284,9 +284,9 @@ fn initialize_prefix_with_proton(
log_install(&format!("STEAM_COMPAT_DATA_PATH={:?}", compat_data_path));
- // Collect all env vars upfront so build_command can forward them
- // via --env= flags in Flatpak mode.
- let mut envs: Vec<(&str, String)> = vec![
+ // Collect all env vars upfront so build_command can set them on the
+ // process (inherited by SLR/pressure-vessel into the container).
+ let envs: Vec<(&str, String)> = vec![
("STEAM_COMPAT_CLIENT_INSTALL_PATH", steam_root.clone()),
("STEAM_COMPAT_DATA_PATH", compat_data_path.display().to_string()),
("SteamAppId", app_id.to_string()),
diff --git a/libs/nak/src/runtime_wrap.rs b/libs/nak/src/runtime_wrap.rs
index 37980d3..943a329 100644
--- a/libs/nak/src/runtime_wrap.rs
+++ b/libs/nak/src/runtime_wrap.rs
@@ -2,15 +2,54 @@ use std::ffi::OsStr;
use std::process::Command;
/// Build a command to run `exe` with the given environment variables.
+///
+/// If SLR (Steam Linux Runtime) is installed, the command is wrapped inside
+/// the pressure-vessel container via the SLR `run` script. Environment
+/// variables are set on the process and inherited by pressure-vessel into the
+/// container (matching how game launches work via QProcess). This ensures
+/// Wine/Proton commands use the container's libraries instead of potentially
+/// broken host libraries.
pub fn build_command<S: AsRef<OsStr>>(
exe: impl AsRef<OsStr>,
envs: &[(&str, S)],
) -> Command {
- let mut cmd = Command::new(exe);
- for (key, value) in envs {
- cmd.env(key, value.as_ref());
+ if let Some(slr_script) = crate::slr::get_slr_run_script() {
+ let mut cmd = Command::new(&slr_script);
+ // Set env vars on the process — pressure-vessel inherits them
+ for (key, value) in envs {
+ cmd.env(key, value.as_ref());
+ }
+ // Expose the executable's parent directory to the container —
+ // needed for system-installed Protons (e.g. /usr/share/steam/...)
+ // whose files may not be visible inside the container by default.
+ if let Some(parent) = std::path::Path::new(exe.as_ref()).parent() {
+ if parent.exists() {
+ let mut flag = std::ffi::OsString::from("--filesystem=");
+ flag.push(parent.as_os_str());
+ cmd.arg(flag);
+ }
+ }
+ // Also expose WINEPREFIX if set in env vars
+ for (key, value) in envs {
+ if *key == "WINEPREFIX" || *key == "STEAM_COMPAT_DATA_PATH" {
+ let path = std::path::Path::new(value.as_ref());
+ if path.exists() || path.parent().map_or(false, |p| p.exists()) {
+ let mut flag = std::ffi::OsString::from("--filesystem=");
+ flag.push(value.as_ref());
+ cmd.arg(flag);
+ }
+ }
+ }
+ cmd.arg("--");
+ cmd.arg(exe);
+ cmd
+ } else {
+ let mut cmd = Command::new(exe);
+ for (key, value) in envs {
+ cmd.env(key, value.as_ref());
+ }
+ cmd
}
- cmd
}
/// Build a command with no extra environment variables.
diff --git a/libs/nak/src/slr.rs b/libs/nak/src/slr.rs
index 30dca38..3fe7d92 100644
--- a/libs/nak/src/slr.rs
+++ b/libs/nak/src/slr.rs
@@ -1,259 +1,231 @@
-//! Steam Linux Runtime (SLR) download and management for Fluorine Manager.
-//!
-//! Downloads SteamLinuxRuntime_sniper from Valve's official repo and stores it
-//! at `~/.local/share/fluorine/steamrt/`. The `run` script is then used to
-//! wrap game launches inside the pressure-vessel container, providing
-//! GStreamer, 32-bit libs, and an FHS-compliant environment for non-FHS
-//! distros (NixOS, etc.).
-
-use std::error::Error;
-use std::fs;
-use std::io::{self, Read, Write};
-use std::path::PathBuf;
-use std::process::Command;
-use std::sync::atomic::{AtomicI32, Ordering};
-
-use crate::logging::{log_info, log_warning};
-
-const BASE_URL: &str =
- "https://repo.steampowered.com/steamrt3/images/latest-public-beta";
-const ARCHIVE_NAME: &str = "SteamLinuxRuntime_sniper.tar.xz";
-const EXTRACTED_DIR: &str = "SteamLinuxRuntime_sniper";
-
-/// Directory where SLR is installed: `~/.local/share/fluorine/steamrt/`
-pub fn slr_install_dir() -> PathBuf {
- crate::paths::data_dir().join("steamrt")
-}
-
-/// Path to the `run` script inside the extracted SLR.
-pub fn slr_run_script() -> PathBuf {
- slr_install_dir().join(EXTRACTED_DIR).join("run")
-}
-
-/// Path where we store the remote BUILD_ID for update checks.
-fn local_build_id_path() -> PathBuf {
- slr_install_dir().join("BUILD_ID.txt")
-}
-
-/// Returns true if the SLR `run` script is present and executable.
-pub fn is_slr_installed() -> bool {
- let script = slr_run_script();
- if !script.exists() {
- return false;
- }
- // Verify it's actually executable
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- if let Ok(meta) = fs::metadata(&script) {
- return meta.permissions().mode() & 0o111 != 0;
- }
- return false;
- }
- #[cfg(not(unix))]
- true
-}
-
-/// Returns the path to the `run` script, or None if SLR is not installed.
-pub fn get_slr_run_script() -> Option<PathBuf> {
- if is_slr_installed() {
- Some(slr_run_script())
- } else {
- None
- }
-}
-
-/// Fetch the remote BUILD_ID as a string.
-fn fetch_remote_build_id() -> Result<String, Box<dyn Error>> {
- let url = format!("{}/BUILD_ID.txt", BASE_URL);
- let resp = ureq::get(&url).call()?;
- let mut body = String::new();
- resp.into_reader().read_to_string(&mut body)?;
- Ok(body.trim().to_string())
-}
-
-/// Read the locally cached BUILD_ID, if any.
-fn read_local_build_id() -> Option<String> {
- fs::read_to_string(local_build_id_path())
- .ok()
- .map(|s| s.trim().to_string())
-}
-
-/// Fetch the expected SHA256 hash for the archive from the remote SHA256SUMS file.
-fn fetch_expected_sha256() -> Result<String, Box<dyn Error>> {
- let url = format!("{}/SHA256SUMS", BASE_URL);
- let resp = ureq::get(&url).call()?;
- let mut body = String::new();
- resp.into_reader().read_to_string(&mut body)?;
-
- for line in body.lines() {
- // Format: "<hash> <filename>" or "<hash> *<filename>"
- let parts: Vec<&str> = line.splitn(2, ' ').collect();
- if parts.len() == 2 {
- let hash = parts[0].trim();
- let name = parts[1].trim().trim_start_matches('*');
- if name == ARCHIVE_NAME {
- return Ok(hash.to_string());
- }
- }
- }
- Err(format!("SHA256 hash for {} not found in SHA256SUMS", ARCHIVE_NAME).into())
-}
-
-/// Verify a file's SHA256 hash using the system `sha256sum` command.
-fn verify_sha256(file: &std::path::Path, expected: &str) -> Result<(), Box<dyn Error>> {
- let output = Command::new("sha256sum").arg(file).output()?;
- if !output.status.success() {
- return Err("sha256sum command failed".into());
- }
- let stdout = String::from_utf8_lossy(&output.stdout);
- let actual = stdout.split_whitespace().next().unwrap_or("").trim();
- if actual != expected {
- return Err(format!(
- "SHA256 mismatch: expected {}, got {}",
- expected, actual
- )
- .into());
- }
- Ok(())
-}
-
-/// Download the archive with streaming progress.
-///
-/// `progress_cb` receives values in 0.0..=1.0.
-/// `cancel_flag` is polled each chunk — set to non-zero to abort.
-fn download_archive(
- dest: &std::path::Path,
- progress_cb: &impl Fn(f32),
- cancel_flag: &AtomicI32,
-) -> Result<(), Box<dyn Error>> {
- let url = format!("{}/{}", BASE_URL, ARCHIVE_NAME);
- let resp = ureq::get(&url).call()?;
-
- // Try to get Content-Length for progress reporting
- let total_bytes: Option<u64> = resp
- .header("Content-Length")
- .and_then(|v| v.parse().ok());
-
- let mut reader = resp.into_reader();
- let mut file = fs::File::create(dest)?;
- let mut buf = vec![0u8; 64 * 1024]; // 64 KiB chunks
- let mut downloaded: u64 = 0;
-
- loop {
- if cancel_flag.load(Ordering::Relaxed) != 0 {
- drop(file);
- let _ = fs::remove_file(dest);
- return Err("Download cancelled".into());
- }
-
- let n = reader.read(&mut buf)?;
- if n == 0 {
- break;
- }
- file.write_all(&buf[..n])?;
- downloaded += n as u64;
-
- if let Some(total) = total_bytes {
- if total > 0 {
- progress_cb((downloaded as f32) / (total as f32));
- }
- }
- }
-
- file.flush()?;
- Ok(())
-}
-
-/// Download and install the Steam Linux Runtime (sniper).
-///
-/// - Skips download if already at the latest BUILD_ID.
-/// - Calls `status_cb` with human-readable status strings.
-/// - Calls `progress_cb` with 0.0..=1.0 during the download phase.
-/// - Polls `cancel_flag`; returns an error if it becomes non-zero.
-pub fn download_slr(
- progress_cb: impl Fn(f32),
- status_cb: impl Fn(&str),
- cancel_flag: &AtomicI32,
-) -> Result<(), Box<dyn Error>> {
- // Check for updates
- status_cb("Checking Steam Linux Runtime version...");
- let remote_build_id = fetch_remote_build_id()?;
- let local_build_id = read_local_build_id();
-
- if local_build_id.as_deref() == Some(remote_build_id.as_str()) && is_slr_installed() {
- log_info("Steam Linux Runtime is already up to date");
- status_cb("Steam Linux Runtime is already up to date");
- progress_cb(1.0);
- return Ok(());
- }
-
- log_info(&format!(
- "Downloading Steam Linux Runtime (BUILD_ID: {})",
- remote_build_id
- ));
-
- let install_dir = slr_install_dir();
- fs::create_dir_all(&install_dir)?;
-
- // Temp file in the install dir
- let archive_path = install_dir.join(ARCHIVE_NAME);
-
- // Download
- status_cb("Downloading Steam Linux Runtime (sniper, ~180 MB)...");
- download_archive(&archive_path, &progress_cb, cancel_flag)?;
- progress_cb(1.0);
-
- // SHA256 verification
- status_cb("Verifying download...");
- match fetch_expected_sha256() {
- Ok(expected) => {
- if let Err(e) = verify_sha256(&archive_path, &expected) {
- let _ = fs::remove_file(&archive_path);
- return Err(format!("Checksum verification failed: {}", e).into());
- }
- log_info("SHA256 verification passed");
- }
- Err(e) => {
- log_warning(&format!(
- "Could not fetch SHA256SUMS ({}), skipping verification",
- e
- ));
- }
- }
-
- // Extract
- status_cb("Extracting Steam Linux Runtime...");
- let extracted = install_dir.join(EXTRACTED_DIR);
- // Remove old copy if present before extracting
- if extracted.exists() {
- fs::remove_dir_all(&extracted)?;
- }
-
- let status = Command::new("tar")
- .args(["xJf", archive_path.to_str().unwrap_or("")])
- .current_dir(&install_dir)
- .status()?;
-
- let _ = fs::remove_file(&archive_path);
-
- if !status.success() {
- return Err(format!("tar extraction failed with status: {}", status).into());
- }
-
- // Sanity check — run script must now exist
- if !slr_run_script().exists() {
- return Err(format!(
- "Extraction succeeded but run script not found at {:?}",
- slr_run_script()
- )
- .into());
- }
-
- // Save BUILD_ID for future update checks
- fs::write(local_build_id_path(), &remote_build_id)?;
-
- log_info("Steam Linux Runtime installed successfully");
- status_cb("Steam Linux Runtime ready");
- Ok(())
-}
+//! Steam Linux Runtime (SLR) download and management for Fluorine Manager.
+//!
+//! Downloads SteamLinuxRuntime_sniper from Valve's official repo and stores it
+//! at `~/.local/share/fluorine/steamrt/`. The `run` script is then used to
+//! wrap game launches inside the pressure-vessel container, providing
+//! GStreamer, 32-bit libs, and an FHS-compliant environment for non-FHS
+//! distros (NixOS, etc.).
+
+use std::error::Error;
+use std::fs;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use std::process::Command;
+use std::sync::atomic::{AtomicI32, Ordering};
+
+use crate::logging::log_info;
+
+const BASE_URL: &str =
+ "https://repo.steampowered.com/steamrt3/images/latest-public-beta";
+const ARCHIVE_NAME: &str = "SteamLinuxRuntime_sniper.tar.xz";
+const EXTRACTED_DIR: &str = "SteamLinuxRuntime_sniper";
+
+/// Directory where SLR is installed: `~/.local/share/fluorine/steamrt/`
+pub fn slr_install_dir() -> PathBuf {
+ crate::paths::data_dir().join("steamrt")
+}
+
+/// Path to the `run` script inside the extracted SLR.
+pub fn slr_run_script() -> PathBuf {
+ slr_install_dir().join(EXTRACTED_DIR).join("run")
+}
+
+/// Path where we store the remote BUILD_ID for update checks.
+fn local_build_id_path() -> PathBuf {
+ slr_install_dir().join("BUILD_ID.txt")
+}
+
+/// Returns true if the SLR `run` script is present and executable.
+pub fn is_slr_installed() -> bool {
+ let script = slr_run_script();
+ if !script.exists() {
+ return false;
+ }
+ // Verify it's actually executable
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+ if let Ok(meta) = fs::metadata(&script) {
+ return meta.permissions().mode() & 0o111 != 0;
+ }
+ return false;
+ }
+ #[cfg(not(unix))]
+ true
+}
+
+/// Returns the path to the `run` script, or None if SLR is not installed.
+pub fn get_slr_run_script() -> Option<PathBuf> {
+ if is_slr_installed() {
+ Some(slr_run_script())
+ } else {
+ None
+ }
+}
+
+/// Fetch the remote BUILD_ID as a string.
+fn fetch_remote_build_id() -> Result<String, Box<dyn Error>> {
+ let url = format!("{}/BUILD_ID.txt", BASE_URL);
+ let resp = ureq::get(&url).call()?;
+ let mut body = String::new();
+ resp.into_reader().read_to_string(&mut body)?;
+ Ok(body.trim().to_string())
+}
+
+/// Read the locally cached BUILD_ID, if any.
+fn read_local_build_id() -> Option<String> {
+ fs::read_to_string(local_build_id_path())
+ .ok()
+ .map(|s| s.trim().to_string())
+}
+
+/// Verify downloaded file size matches the Content-Length from the HTTP response.
+/// This is more reliable than SHA256SUMS which can be stale on Valve's CDN.
+fn verify_download_size(file: &std::path::Path, expected: u64) -> Result<(), Box<dyn Error>> {
+ let actual = fs::metadata(file)?.len();
+ if actual != expected {
+ return Err(format!(
+ "Download incomplete: expected {} bytes, got {}",
+ expected, actual
+ )
+ .into());
+ }
+ Ok(())
+}
+
+/// Download the archive with streaming progress.
+///
+/// `progress_cb` receives values in 0.0..=1.0.
+/// `cancel_flag` is polled each chunk — set to non-zero to abort.
+/// Returns the Content-Length on success for verification.
+fn download_archive(
+ dest: &std::path::Path,
+ progress_cb: &impl Fn(f32),
+ cancel_flag: &AtomicI32,
+) -> Result<Option<u64>, Box<dyn Error>> {
+ let url = format!("{}/{}", BASE_URL, ARCHIVE_NAME);
+ let resp = ureq::get(&url).call()?;
+
+ let total_bytes: Option<u64> = resp
+ .header("Content-Length")
+ .and_then(|v| v.parse().ok());
+
+ let mut reader = resp.into_reader();
+ let mut file = fs::File::create(dest)?;
+ let mut buf = vec![0u8; 64 * 1024]; // 64 KiB chunks
+ let mut downloaded: u64 = 0;
+
+ loop {
+ if cancel_flag.load(Ordering::Relaxed) != 0 {
+ drop(file);
+ let _ = fs::remove_file(dest);
+ return Err("Download cancelled".into());
+ }
+
+ let n = reader.read(&mut buf)?;
+ if n == 0 {
+ break;
+ }
+ file.write_all(&buf[..n])?;
+ downloaded += n as u64;
+
+ if let Some(total) = total_bytes {
+ if total > 0 {
+ progress_cb((downloaded as f32) / (total as f32));
+ }
+ }
+ }
+
+ file.flush()?;
+ Ok(total_bytes)
+}
+
+/// Download and install the Steam Linux Runtime (sniper).
+///
+/// - Skips download if already at the latest BUILD_ID.
+/// - Calls `status_cb` with human-readable status strings.
+/// - Calls `progress_cb` with 0.0..=1.0 during the download phase.
+/// - Polls `cancel_flag`; returns an error if it becomes non-zero.
+pub fn download_slr(
+ progress_cb: impl Fn(f32),
+ status_cb: impl Fn(&str),
+ cancel_flag: &AtomicI32,
+) -> Result<(), Box<dyn Error>> {
+ // Check for updates
+ status_cb("Checking Steam Linux Runtime version...");
+ let remote_build_id = fetch_remote_build_id()?;
+ let local_build_id = read_local_build_id();
+
+ if local_build_id.as_deref() == Some(remote_build_id.as_str()) && is_slr_installed() {
+ log_info("Steam Linux Runtime is already up to date");
+ status_cb("Steam Linux Runtime is already up to date");
+ progress_cb(1.0);
+ return Ok(());
+ }
+
+ log_info(&format!(
+ "Downloading Steam Linux Runtime (BUILD_ID: {})",
+ remote_build_id
+ ));
+
+ let install_dir = slr_install_dir();
+ fs::create_dir_all(&install_dir)?;
+
+ // Temp file in the install dir
+ let archive_path = install_dir.join(ARCHIVE_NAME);
+
+ // Download
+ status_cb("Downloading Steam Linux Runtime (sniper, ~180 MB)...");
+ let content_length = download_archive(&archive_path, &progress_cb, cancel_flag)?;
+ progress_cb(1.0);
+
+ // Verify download integrity via Content-Length.
+ // We don't use Valve's SHA256SUMS file because their CDN frequently
+ // serves a stale copy that doesn't match the current archive.
+ if let Some(expected_size) = content_length {
+ status_cb("Verifying download...");
+ if let Err(e) = verify_download_size(&archive_path, expected_size) {
+ let _ = fs::remove_file(&archive_path);
+ return Err(format!("Download verification failed: {}", e).into());
+ }
+ log_info(&format!(
+ "Download verified ({} bytes)",
+ expected_size
+ ));
+ }
+
+ // Extract
+ status_cb("Extracting Steam Linux Runtime...");
+ let extracted = install_dir.join(EXTRACTED_DIR);
+ // Remove old copy if present before extracting
+ if extracted.exists() {
+ fs::remove_dir_all(&extracted)?;
+ }
+
+ let status = Command::new("tar")
+ .args(["xJf", archive_path.to_str().unwrap_or("")])
+ .current_dir(&install_dir)
+ .status()?;
+
+ let _ = fs::remove_file(&archive_path);
+
+ if !status.success() {
+ return Err(format!("tar extraction failed with status: {}", status).into());
+ }
+
+ // Sanity check — run script must now exist
+ if !slr_run_script().exists() {
+ return Err(format!(
+ "Extraction succeeded but run script not found at {:?}",
+ slr_run_script()
+ )
+ .into());
+ }
+
+ // Save BUILD_ID for future update checks
+ fs::write(local_build_id_path(), &remote_build_id)?;
+
+ log_info("Steam Linux Runtime installed successfully");
+ status_cb("Steam Linux Runtime ready");
+ Ok(())
+}
diff --git a/libs/nak_ffi/CMakeLists.txt b/libs/nak_ffi/CMakeLists.txt
index bdfac9a..72d939f 100644
--- a/libs/nak_ffi/CMakeLists.txt
+++ b/libs/nak_ffi/CMakeLists.txt
@@ -12,14 +12,17 @@ endif()
set(NAK_FFI_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(NAK_FFI_LIB ${NAK_FFI_DIR}/target/${CARGO_BUILD_TYPE}/libnak_ffi.so)
+# Glob all Rust sources so any change triggers a cargo rebuild
+file(GLOB_RECURSE NAK_FFI_SOURCES ${NAK_FFI_DIR}/src/*.rs)
+file(GLOB_RECURSE NAK_SOURCES ${NAK_FFI_DIR}/../nak/src/*.rs)
+
add_custom_command(
OUTPUT ${NAK_FFI_LIB}
COMMAND cargo build ${CARGO_BUILD_FLAGS}
WORKING_DIRECTORY ${NAK_FFI_DIR}
COMMENT "Building NaK FFI library (Rust)"
- DEPENDS ${NAK_FFI_DIR}/Cargo.toml ${NAK_FFI_DIR}/src/lib.rs
- ${NAK_FFI_DIR}/../nak/Cargo.toml ${NAK_FFI_DIR}/../nak/src/lib.rs
- ${NAK_FFI_DIR}/../nak/src/icons.rs
+ DEPENDS ${NAK_FFI_DIR}/Cargo.toml ${NAK_FFI_DIR}/../nak/Cargo.toml
+ ${NAK_FFI_SOURCES} ${NAK_SOURCES}
)
add_custom_target(nak_ffi_build DEPENDS ${NAK_FFI_LIB})
diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp
index 3c279fa..c6fc545 100644
--- a/src/src/protonlauncher.cpp
+++ b/src/src/protonlauncher.cpp
@@ -463,9 +463,9 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const
// Build: [wrappers] run_script [--filesystem=...] -- proton_script protonArgs
QStringList slrArgs;
- // Expose the game directory (and its FUSE-mounted Data/) to the
- // pressure-vessel container. Without this, the container's mount
- // namespace may not see FUSE mounts on the host.
+ // Expose host directories to the pressure-vessel container.
+ // Without --filesystem= flags, the container's mount namespace
+ // may not see FUSE mounts or system-installed Proton paths.
if (!m_binary.isEmpty()) {
const QString gameDir = QFileInfo(m_binary).absolutePath();
slrArgs << QStringLiteral("--filesystem=%1").arg(gameDir);
@@ -473,6 +473,13 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const
if (!m_prefixPath.isEmpty()) {
slrArgs << QStringLiteral("--filesystem=%1").arg(m_prefixPath);
}
+ // Expose the Proton installation directory — needed for
+ // system-installed Protons (e.g. /usr/share/steam/compatibilitytools.d/)
+ // whose files may not be visible inside the container by default.
+ {
+ const QString protonDir = QFileInfo(protonScript).absolutePath();
+ slrArgs << QStringLiteral("--filesystem=%1").arg(protonDir);
+ }
slrArgs << "--" << protonScript << protonArgs;
wrapProgram(m_wrapperCommands, runScript, slrArgs, program, arguments);