Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
4d08990
Implement Ghost Steam (Windows Runtime) architecture
google-labs-jules[bot] Feb 18, 2026
66d2f5c
Fix Ghost Steam background launch environment and ensure Proton consi…
google-labs-jules[bot] Feb 19, 2026
16716a7
Fix Ghost Steam network errors and improve DRM-free isolation
google-labs-jules[bot] Feb 19, 2026
045a5b5
Fix installer environment and implement credential synchronization
google-labs-jules[bot] Feb 19, 2026
c62fbaa
Implement robust Ghost Steam installation using raw Wine binary
google-labs-jules[bot] Feb 19, 2026
cdcb989
Revert Ghost Steam installation to use Proton script and fix env vars
google-labs-jules[bot] Feb 19, 2026
e880f64
Implement Ghost Steam (Windows Runtime) architecture
google-labs-jules[bot] Feb 19, 2026
ef8bf11
Refine Ghost Steam to use interactive first-time setup
google-labs-jules[bot] Feb 19, 2026
8f67860
Rewrite Ghost Steam installation to use Raw Wine
google-labs-jules[bot] Feb 19, 2026
c9166c8
Integrate Lutris workarounds for Ghost Steam stability
google-labs-jules[bot] Feb 19, 2026
5662929
Fix Ghost Steam connectivity and override persistence
google-labs-jules[bot] Feb 19, 2026
7dd26ab
Fix CI 403 Forbidden and complete Ghost Steam architecture
google-labs-jules[bot] Feb 19, 2026
9cf35bd
Restore GUI rendering for Ghost Steam installer and first run
google-labs-jules[bot] Feb 19, 2026
1ed8182
Implement Master Steam Runtime architecture
google-labs-jules[bot] Feb 20, 2026
d344e2b
Implement Phase 1-4 of the new 'Ghost Steam' (Windows Runtime) archit…
google-labs-jules[bot] Feb 20, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: SteamFlow Rust Build

permissions:
contents: read
actions: write

on:
push:
paths:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ serde_json = "1"
steam-vdf-parser = "0.1.1" # Needed for raw binary PICS VDF parsing (not supported by keyvalues-serde or steam-vent yet)
steam-vent = "0.4.2"
steam-vent-proto = "=0.5.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "time"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs", "time", "process"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
walkdir = "2"
Expand Down
54 changes: 53 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::models::{OwnedGame, SessionState, UserConfigStore};
use crate::models::{OwnedGame, SessionState};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand All @@ -11,6 +11,34 @@ pub struct GameConfig {
pub platform_preference: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserAppConfig {
pub launch_options: String, // e.g. "-novid -console"
pub env_variables: HashMap<String, String>, // e.g. {"MANGOHUD": "1"}
pub hidden: bool, // Future use
pub favorite: bool, // Future use
#[serde(default = "default_true")]
pub use_steam_runtime: bool,
}

fn default_true() -> bool {
true
}

impl Default for UserAppConfig {
fn default() -> Self {
Self {
launch_options: String::new(),
env_variables: HashMap::new(),
hidden: false,
favorite: false,
use_steam_runtime: true,
}
}
}

pub type UserConfigStore = HashMap<u32, UserAppConfig>;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LauncherConfig {
pub steam_library_path: String,
Expand Down Expand Up @@ -163,6 +191,30 @@ pub async fn save_launcher_config(config: &LauncherConfig) -> Result<()> {
Ok(())
}

pub fn absolute_path(path: impl AsRef<std::path::Path>) -> Result<PathBuf> {
let path = path.as_ref();
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
};

// Normalize: remove "." and handle ".."
let mut components = abs_path.components().peekable();
let mut ret = PathBuf::new();

while let Some(c) = components.next() {
match c {
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
ret.pop();
}
_ => ret.push(c),
}
}
Ok(ret)
}

pub fn library_cache_path() -> Result<PathBuf> {
Ok(data_dir()?.join("library_cache.json"))
}
Expand Down
169 changes: 169 additions & 0 deletions src/launch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use tokio::process::Command;
use crate::utils::ensure_steam_installer;

pub fn build_runner_command(runner_path: &Path) -> Result<Command> {
// 1. Check if it's Proton (has a 'proton' script in the root)
let proton_script = runner_path.join("proton");
if proton_script.exists() {
let mut cmd = Command::new(proton_script);
cmd.arg("run");
return Ok(cmd);
}

// 2. Check if it's standard Wine / wine-tkg (has 'bin/wine')
let wine_bin = runner_path.join("bin/wine");
if wine_bin.exists() {
return Ok(Command::new(wine_bin));
}

// 3. Fallback for system wine if runner_path is just "/usr/bin/wine"
if runner_path.is_file() && runner_path.file_name() == Some(std::ffi::OsStr::new("wine")) {
return Ok(Command::new(runner_path));
}

Err(anyhow::anyhow!("failed to find wine binary in runner directory: {:?}", runner_path))
}

pub async fn install_ghost_steam(app_id: u32, proton_path: &Path) -> Result<()> {
println!("Starting Steam Runtime Installation for AppID: {}...", app_id);
let launcher_config = crate::config::load_launcher_config().await?;
let library_root = crate::config::absolute_path(&launcher_config.steam_library_path)?;
let prefix = library_root.join("steamapps/compatdata").join(app_id.to_string());

let installer_path = ensure_steam_installer().await?;
println!("Installer Path: {}", installer_path.display());
println!("Prefix Path: {}", prefix.display());

// Step A: Sanitize Target
let target_dir = prefix.join("pfx/drive_c/Program Files (x86)/Steam");
if target_dir.exists() {
println!("Sanitizing prefix...");
let mut deleted_count = 0;
let entries = ["steam.exe", "lsteamclient.dll"];
for entry in entries {
let file_path = target_dir.join(entry);
if file_path.exists() {
std::fs::remove_file(&file_path)?;
deleted_count += 1;
}
}
println!("Sanitizing prefix... deleted {} files.", deleted_count);
}

// Step B: Construct Command
let mut cmd = build_runner_command(proton_path)?;
cmd.arg(&installer_path).arg("/S");

let abs_prefix = crate::config::absolute_path(prefix.join("pfx"))?;
cmd.env("WINEPREFIX", &abs_prefix);
cmd.env("WINEDLLOVERRIDES", "steam.exe=n;lsteamclient=n;steam_api=n;steam_api64=n;steamclient=n");

// REMOVE: SteamAppId, SteamGameId, STEAM_COMPAT_CLIENT_INSTALL_PATH
cmd.env_remove("SteamAppId");
cmd.env_remove("SteamGameId");
cmd.env_remove("STEAM_COMPAT_CLIENT_INSTALL_PATH");

// Fix TLS/Network Error
cmd.env("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt");
cmd.env("SSL_CERT_DIR", "/etc/ssl/certs");

// Pass through display variables for installer UI (even if silent, sometimes needed)
for var in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR"] {
if let Ok(val) = std::env::var(var) {
cmd.env(var, val);
}
}

// Step C: Execute & Wait
let status = cmd.status().await.context("failed to run Steam installer")?;
println!("Installer finished with exit code: {}", status);

Ok(())
}

pub async fn launch_ghost_steam(
app_id: u32,
proton_path: &Path,
steam_exe: &Path,
prefix: &Path,
silent: bool,
) -> Result<()> {
if silent {
println!("Launching Steam Runtime in background...");
} else {
println!("Launching Steam Runtime interactively...");
}

let mut cmd = build_runner_command(proton_path)?;
if silent {
cmd.arg(steam_exe).arg("-silent").arg("-no-browser").arg("-noverifyfiles").arg("-tcp").arg("-cef-disable-gpu-compositing");
} else {
cmd.arg(steam_exe).arg("-tcp").arg("-cef-disable-gpu-compositing");
}

let abs_prefix = crate::config::absolute_path(prefix.join("pfx"))?;
cmd.env("WINEPREFIX", &abs_prefix);
cmd.env("STEAM_COMPAT_DATA_PATH", crate::config::absolute_path(prefix)?);

if app_id != 0 {
cmd.env("SteamAppId", app_id.to_string());
}

// Ensure it uses native binaries
cmd.env("WINEDLLOVERRIDES", "steam.exe=n;lsteamclient=n;steam_api=n;steam_api64=n;steamclient=n");

// Fix TLS/Network Error
cmd.env("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt");
cmd.env("SSL_CERT_DIR", "/etc/ssl/certs");

// Pass through display variables
for var in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR"] {
if let Ok(val) = std::env::var(var) {
cmd.env(var, val);
}
}

if silent {
let _child = cmd.spawn().context("failed to launch Ghost Steam")?;
println!("Waiting for Steam Runtime initialization...");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
} else {
let status = cmd.status().await.context("failed to run interactive Steam")?;
println!("Interactive Steam exited with status: {}", status);
}

Ok(())
}

pub fn get_installed_steam_path(prefix: &Path) -> Option<PathBuf> {
let pfx_path = prefix.join("pfx");
if !pfx_path.exists() {
return None;
}

let drive_c = pfx_path.join("drive_c");
let search_paths = [
drive_c.join("Program Files (x86)/Steam"),
drive_c.join("Program Files/Steam"),
drive_c.join("Steam"),
];

for path in search_paths {
if path.exists() {
if let Ok(entries) = std::fs::read_dir(&path) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_ascii_lowercase();
if name == "steam.exe" {
println!("Steam Executable Found: Yes ({})", entry.path().display());
return Some(entry.path());
}
}
}
}
}

println!("Steam Executable Found: No");
None
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ pub mod library;
pub mod models;
pub mod steam_client;
pub mod ui;
pub mod utils;
pub mod launch;
9 changes: 0 additions & 9 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UserAppConfig {
pub launch_options: String, // e.g. "-novid -console"
pub env_variables: HashMap<String, String>, // e.g. {"MANGOHUD": "1"}
pub hidden: bool, // Future use
pub favorite: bool, // Future use
}

pub type UserConfigStore = HashMap<u32, UserAppConfig>;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SessionState {
Expand Down
Loading