Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 96 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ aes = "0.8"
steam-cdn = "1.0.0"
libc = "0.2"
regex = "1"
rhai = { version = "1.19", features = ["sync"] }

[patch.crates-io]
steam-cdn = { path = "vendor/steam-cdn" }
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ pub async fn ensure_config_dirs() -> Result<()> {
fs::create_dir_all(&config).await?;
let images = opensteam_image_cache_dir()?;
fs::create_dir_all(&images).await?;

// Seed default Rhai fixups
let _ = crate::launch::fixups::seed_default_fixups();

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions src/infra/logging/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ pub struct LaunchVerification {
pub steam_running_before_launch: bool,
pub steam_auto_start_attempted: bool,
pub steam_auto_start_failed: bool,
pub protonfixes_routed: bool,
pub rhai_fixup_applied: Option<String>,
pub steam_api_initialized: Option<bool>,
pub steam_ownership_confirmed: Option<bool>,
pub steam_client_artifact: Option<String>, // "local", "windows", "host"
Expand Down
2 changes: 2 additions & 0 deletions src/infra/runners/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod tests {
proton_path: Some("/tmp/proton".to_string()),
target_architecture: crate::models::ExecutableArchitecture::X86_64,
dll_resolutions: Vec::new(),
fixup_result: None,
verification_ptr: std::ptr::null_mut(),
}
}
Expand Down Expand Up @@ -112,6 +113,7 @@ mod tests {
proton_path: Some(runner_path.to_string_lossy().to_string()),
target_architecture: crate::models::ExecutableArchitecture::X86_64,
dll_resolutions: Vec::new(),
fixup_result: None,
verification_ptr: std::ptr::null_mut(),
};

Expand Down
1 change: 1 addition & 0 deletions src/infra/runners/trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct LaunchContext {
pub proton_path: Option<String>,
pub target_architecture: crate::models::ExecutableArchitecture,
pub dll_resolutions: Vec<crate::launch::dll_provider_resolver::DllResolution>,
pub fixup_result: Option<crate::launch::fixups::FixupResult>,
pub verification_ptr: *mut crate::infra::logging::LaunchVerification, // HACK: for Runner to write diagnostics
}

Expand Down
90 changes: 80 additions & 10 deletions src/infra/runners/wine_tkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,44 @@ impl Runner for WineTkgRunner {
if steam_running {
println!("✅ Steam already running in prefix — skipping spawn");
} else {
let steam_runner = if !ctx.launcher_config.steam_runtime_runner.as_os_str().is_empty() {
let steam_runner_path = if !ctx.launcher_config.steam_runtime_runner.as_os_str().is_empty() {
ctx.launcher_config.steam_runtime_runner.clone()
} else {
active_runner.clone()
let r = crate::utils::resolve_runner("wine-tkg", &library_root);
if !r.exists() {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
"Steam Runtime Runner is not set and 'wine-tkg' was not found in common locations. Please set 'Steam Runtime Runner' in Settings."
));
}
r
};

tracing::info!("Using runner for background Steam: {}", steam_runner.display());
tracing::info!("Using runner for background Steam: {}", steam_runner_path.display());
let steam_runner_kind = crate::utils::classify_runner(&steam_runner_path);

let mut steam_cmd = match steam_runner_kind {
crate::utils::RunnerKind::PlainWine { wine64, .. } => {
Command::new(wine64)
}
crate::utils::RunnerKind::Proton { bundled_wine64, .. } => {
if let Some(wine64) = bundled_wine64 {
Command::new(wine64)
} else {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
format!("Proton tree at {} has no bundled wine64 binary for bare-wine invocation.", steam_runner_path.display())
));
}
}
crate::utils::RunnerKind::Unknown => {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
format!("Failed to classify Steam Runtime runner at {}", steam_runner_path.display())
));
}
};

let mut steam_cmd = crate::utils::build_runner_command(&steam_runner)
.map_err(|e| LaunchError::new(LaunchErrorKind::Runner, format!("Invalid Steam Runtime runner path: {}", steam_runner.display())).with_source(e))?;
steam_cmd.current_dir(&prefix_steam_dir);
steam_cmd
.arg("C:\\Program Files (x86)\\Steam\\steam.exe")
Expand Down Expand Up @@ -526,6 +554,16 @@ impl Runner for WineTkgRunner {
strict_dxvk,
);

// Apply Rhai fixup DLL overrides
if let Some(res) = &ctx.fixup_result {
for fragment in &res.extra_dll_overrides {
if !dll_overrides.is_empty() {
dll_overrides.push(';');
}
dll_overrides.push_str(fragment);
}
}

// Enhance overrides with resolved DLL providers
for res in &ctx.dll_resolutions {
if res.chosen_provider == crate::launch::dll_provider_resolver::DllProvider::GameLocal ||
Expand Down Expand Up @@ -775,6 +813,13 @@ impl Runner for WineTkgRunner {
}
}

// Apply Rhai fixup environment variables
if let Some(res) = &ctx.fixup_result {
for (key, val) in &res.extra_env {
env.insert(key.clone(), val.clone());
}
}

if let Some(config) = &ctx.user_config {
for (key, val) in &config.env_variables {
env.insert(key.clone(), val.clone());
Expand Down Expand Up @@ -851,11 +896,36 @@ impl Runner for WineTkgRunner {

let mut spec = CommandSpec::default();

// Build the base command (handles 'proton run' wrapper and directory resolution)
let base_cmd = crate::utils::build_runner_command(&active_runner)
.map_err(|e| LaunchError::new(LaunchErrorKind::Runner, format!("Invalid Compatibility Layer path: {}", active_runner.display())).with_source(e))?;
spec.program = base_cmd.get_program().into();
spec.args = base_cmd.get_args().map(|s| s.to_string_lossy().to_string()).collect();
let kind = crate::utils::classify_runner(&active_runner);
match kind {
crate::utils::RunnerKind::Proton { has_protonfixes, .. } if has_protonfixes => {
// Build the base command (handles 'proton run' wrapper)
let base_cmd = crate::utils::build_runner_command(&active_runner)
.map_err(|e| LaunchError::new(LaunchErrorKind::Runner, format!("Invalid Compatibility Layer path: {}", active_runner.display())).with_source(e))?;
spec.program = base_cmd.get_program().into();
spec.args = base_cmd.get_args().map(|s| s.to_string_lossy().to_string()).collect();
}
crate::utils::RunnerKind::PlainWine { wine64, .. } => {
spec.program = wine64;
}
crate::utils::RunnerKind::Proton { bundled_wine64, .. } => {
// Stripped Proton build - use bare wine
if let Some(wine64) = bundled_wine64 {
spec.program = wine64;
} else {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
format!("Proton tree at {} has no bundled wine64 binary for bare-wine invocation.", active_runner.display())
));
}
}
crate::utils::RunnerKind::Unknown => {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
format!("Failed to classify Compatibility Layer at {}", active_runner.display())
));
}
}

let install_dir = PathBuf::from(
ctx.app.install_path
Expand Down
Loading