Skip to content
Merged
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", features = ["sync"] }

[patch.crates-io]
steam-cdn = { path = "vendor/steam-cdn" }
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 @@ -76,6 +76,8 @@ pub struct LaunchVerification {
pub steam_runtime_policy: String,
pub steam_runtime_source: String, // "default", "auto", "override"
pub windows_steam_discovery_enabled: bool,
pub protonfixes_routed: bool,
pub rhai_fixup_applied: Option<String>,
pub log_head: Vec<String>,
pub log_tail: Vec<String>,
}
Expand Down
7 changes: 6 additions & 1 deletion 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 @@ -77,6 +78,8 @@ mod tests {
let dxvk_dll = runner_path.join("files/lib/wine/dxvk/d3d11.dll");
fs::create_dir_all(dxvk_dll.parent().unwrap()).unwrap();
fs::write(&dxvk_dll, "fake dll content").unwrap();
fs::create_dir_all(runner_path.join("bin")).unwrap();
fs::write(runner_path.join("bin/wine64"), "").unwrap();

let app = LibraryGame {
app_id: 123,
Expand Down Expand Up @@ -112,6 +115,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 Expand Up @@ -145,7 +149,8 @@ mod tests {

let tmp = tempdir().unwrap();
let proton_path = tmp.path().join("proton");
fs::create_dir_all(&proton_path).unwrap();
fs::create_dir_all(proton_path.join("bin")).unwrap();
fs::write(proton_path.join("bin/wine64"), "").unwrap();
ctx.proton_path = Some(proton_path.to_string_lossy().to_string());

// build_env should succeed without real filesystem
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
72 changes: 66 additions & 6 deletions src/infra/runners/wine_tkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,29 @@ impl Runner for WineTkgRunner {
let steam_runner = if !ctx.launcher_config.steam_runtime_runner.as_os_str().is_empty() {
ctx.launcher_config.steam_runtime_runner.clone()
} else {
active_runner.clone()
let discovered = crate::utils::resolve_runner("wine-tkg", &library_root);
if !discovered.exists() {
return Err(LaunchError::new(
LaunchErrorKind::Runner,
"Steam Runtime Runner is not set and no wine-tkg/plain Wine runner was found. Set “Steam Runtime Runner” in Settings."
));
}
discovered
};

tracing::info!("Using runner for background Steam: {}", steam_runner.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))?;
let mut steam_cmd = match crate::utils::classify_runner(&steam_runner) {
crate::utils::RunnerKind::PlainWine { .. } => 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))?,
crate::utils::RunnerKind::Proton { bundled_wine64: Some(wine64), .. } => Command::new(wine64),
crate::utils::RunnerKind::Proton { bundled_wine64: None, .. } => {
return Err(LaunchError::new(LaunchErrorKind::Runner, format!("Steam Runtime Runner {} is a Proton tree but no bundled wine64 was found", steam_runner.display())));
}
crate::utils::RunnerKind::Unknown => {
return Err(LaunchError::new(LaunchErrorKind::Runner, format!("Unknown Steam Runtime Runner: {}", steam_runner.display())));
}
};
steam_cmd.current_dir(&prefix_steam_dir);
steam_cmd
.arg("C:\\Program Files (x86)\\Steam\\steam.exe")
Expand Down Expand Up @@ -475,6 +491,20 @@ impl Runner for WineTkgRunner {
)
));
}
let game_runner_kind = crate::utils::classify_runner(&active_runner_path);
if matches!(game_runner_kind, crate::utils::RunnerKind::Unknown) {
return Err(LaunchError::new(LaunchErrorKind::Runner, format!("Unknown Compatibility Layer path: {}", active_runner_path.display())));
}
unsafe {
if !ctx.verification_ptr.is_null() {
(*ctx.verification_ptr).protonfixes_routed = matches!(game_runner_kind, crate::utils::RunnerKind::Proton { has_protonfixes: true, .. });
if let Some(result) = &ctx.fixup_result {
if !result.extra_env.is_empty() || !result.extra_dll_overrides.is_empty() || !result.actions_log.is_empty() {
(*ctx.verification_ptr).rhai_fixup_applied.get_or_insert_with(|| ctx.app.app_id.to_string());
}
}
}
}
let _components = crate::utils::detect_runner_components(
&active_runner_path,
Some(&effective_game_prefix),
Expand Down Expand Up @@ -525,6 +555,19 @@ impl Runner for WineTkgRunner {
Some(&game_working_dir),
strict_dxvk,
);
if let Some(fixup) = &ctx.fixup_result {
for fragment in &fixup.extra_dll_overrides {
if !fragment.trim().is_empty() {
if !dll_overrides.is_empty() {
dll_overrides.push(';');
}
dll_overrides.push_str(fragment.trim());
}
}
for action in &fixup.actions_log {
tracing::info!("rhai fixup action: {}", action);
}
}

// Enhance overrides with resolved DLL providers
for res in &ctx.dll_resolutions {
Expand All @@ -550,6 +593,11 @@ impl Runner for WineTkgRunner {

tracing::info!("Final WINEDLLOVERRIDES: {}", dll_overrides);
env.insert("WINEDLLOVERRIDES".to_string(), dll_overrides);
if let Some(fixup) = &ctx.fixup_result {
for (key, value) in &fixup.extra_env {
env.insert(key.clone(), value.clone());
}
}

let steam_prefix_mode = ctx.user_config.as_ref()
.map(|c| c.steam_prefix_mode.clone())
Expand Down Expand Up @@ -848,12 +896,25 @@ impl Runner for WineTkgRunner {
.unwrap_or(ctx.launcher_config.proton_version.as_str())
};
let active_runner = crate::utils::resolve_runner(proton, &library_root);
let game_runner_kind = crate::utils::classify_runner(&active_runner);
if matches!(game_runner_kind, crate::utils::RunnerKind::Unknown) {
return Err(LaunchError::new(LaunchErrorKind::Runner, format!("Unknown Compatibility Layer path: {}", active_runner.display())));
}

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))?;
let base_cmd = match &game_runner_kind {
crate::utils::RunnerKind::Proton { has_protonfixes: false, bundled_wine64: Some(wine64), .. } => Command::new(wine64),
crate::utils::RunnerKind::Proton { has_protonfixes: false, bundled_wine64: None, .. } => {
return Err(LaunchError::new(LaunchErrorKind::Runner, format!("Compatibility Layer {} has a proton script but no protonfixes and no bundled wine64 for fallback", active_runner.display())));
}
// Real Proton must go through the proton script so bundled protonfixes and
// pressure-vessel/bootstrap behavior run naturally. Environment construction is
// shared with Wine so DLL override/WINEDLLPATH handling remains intact.
_ => 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();

Expand Down Expand Up @@ -929,4 +990,3 @@ impl Runner for WineTkgRunner {
cmd.spawn().map_err(|e| LaunchError::new(LaunchErrorKind::Process, "failed to spawn runner process").with_source(anyhow!(e)))
}
}

Loading