Rust · 8165 bytes Raw Blame History
1 //! wanda init - Initialize WANDA
2
3 use clap::Args;
4 use indicatif::{ProgressBar, ProgressStyle};
5 use std::path::PathBuf;
6 use wanda_core::{
7 config::WandaConfig,
8 prefix::{PrefixBuilder, PrefixManager},
9 steam::{ProtonCompatibility, ProtonManager, SteamInstallation},
10 wemod::{WemodDownloader, WemodInstaller},
11 Result, WandaError,
12 };
13
14 #[derive(Args)]
15 pub struct InitArgs {
16 /// Override Steam installation path
17 #[arg(long)]
18 steam_path: Option<PathBuf>,
19
20 /// Specify Proton version to use
21 #[arg(long)]
22 proton: Option<String>,
23
24 /// Skip WeMod installation
25 #[arg(long)]
26 skip_wemod: bool,
27
28 /// Skip .NET Framework installation (WeMod needs it for trainers)
29 #[arg(long)]
30 skip_dotnet: bool,
31
32 /// Force reinitialization even if already set up
33 #[arg(long, short)]
34 force: bool,
35 }
36
37 pub async fn run(args: InitArgs, config_path: Option<PathBuf>) -> Result<()> {
38 println!("Initializing WANDA...\n");
39
40 // Load or create config
41 let mut config = match &config_path {
42 Some(path) => WandaConfig::load_from(path)?,
43 None => WandaConfig::load()?,
44 };
45
46 // Override steam path if provided
47 if let Some(ref path) = args.steam_path {
48 config.steam.install_path = Some(path.clone());
49 }
50
51 // Discover Steam
52 println!("Looking for Steam installation...");
53 let steam = SteamInstallation::discover(&config)?;
54 println!(
55 " Found Steam at: {}{}",
56 steam.root_path.display(),
57 if steam.is_flatpak { " (Flatpak)" } else { "" }
58 );
59 println!(" Libraries: {}", steam.libraries.len());
60
61 let total_games: usize = steam.libraries.iter().map(|l| l.apps.len()).sum();
62 println!(" Games: {}", total_games);
63
64 // Discover Proton
65 println!("\nLooking for Proton versions...");
66 let proton_manager = ProtonManager::discover(&steam, &config)?;
67
68 if proton_manager.versions.is_empty() {
69 return Err(WandaError::ProtonNotFound);
70 }
71
72 // Select Proton version
73 // Priority: 1) --proton arg, 2) config preferred_version, 3) get_recommended()
74 // Experimental/Unsupported versions from config are auto-overridden unless --proton is explicit
75 let proton = if let Some(ref name) = args.proton {
76 let v = proton_manager.find_by_name(name).ok_or_else(|| {
77 eprintln!("Available Proton versions:");
78 for v in &proton_manager.versions {
79 eprintln!(" - {} ({})", v.name, v.compatibility);
80 }
81 WandaError::ProtonNotFound
82 })?;
83 if matches!(v.compatibility, ProtonCompatibility::Experimental | ProtonCompatibility::Unsupported) {
84 eprintln!(
85 " Warning: '{}' is {} — this may cause WeMod to fail",
86 v.name, v.compatibility
87 );
88 }
89 v
90 } else if let Some(ref preferred) = config.proton.preferred_version {
91 match proton_manager.find_by_name(preferred) {
92 Some(v) if matches!(v.compatibility, ProtonCompatibility::Experimental | ProtonCompatibility::Unsupported) => {
93 let recommended = proton_manager.get_recommended().ok_or(WandaError::ProtonNotFound)?;
94 eprintln!(
95 " Configured Proton '{}' is {} — auto-switching to '{}' ({})",
96 v.name, v.compatibility, recommended.name, recommended.compatibility
97 );
98 recommended
99 }
100 Some(v) => v,
101 None => {
102 eprintln!(" Configured Proton '{}' not found, using recommended", preferred);
103 proton_manager.get_recommended().ok_or(WandaError::ProtonNotFound)?
104 }
105 }
106 } else {
107 proton_manager.get_recommended().ok_or(WandaError::ProtonNotFound)?
108 };
109
110 println!(" Using: {} ({})", proton.name, proton.compatibility);
111
112 // Set up prefix
113 println!("\nSetting up Wine prefix...");
114 let mut prefix_manager = PrefixManager::new(&config);
115 prefix_manager.load()?;
116
117 let existing = prefix_manager.get("default");
118 if existing.is_some() && !args.force {
119 println!(" Prefix already exists. Use --force to reinitialize.");
120 } else {
121 if existing.is_some() && args.force {
122 println!(" Removing existing prefix...");
123 prefix_manager.delete("default")?;
124 }
125
126 println!(" Creating prefix (this may take several minutes)...");
127 let pb = ProgressBar::new_spinner();
128 pb.set_style(
129 ProgressStyle::default_spinner()
130 .template("{spinner:.green} {msg}")
131 .unwrap(),
132 );
133 pb.set_message("Initializing Wine prefix...");
134 pb.enable_steady_tick(std::time::Duration::from_millis(100));
135
136 prefix_manager.create("default", proton).await?;
137
138 pb.finish_with_message("Prefix created successfully");
139 }
140
141 // Install .NET Framework 4.8 (required by WeMod's trainer engine)
142 if !args.skip_dotnet {
143 println!("\nInstalling .NET Framework 4.8...");
144 println!(" This may take 10-15 minutes. Use --skip-dotnet to skip.");
145
146 let prefix_path = prefix_manager.base_path.join("default");
147 let dotnet_builder = PrefixBuilder::new(&prefix_path, proton);
148
149 let pb = ProgressBar::new_spinner();
150 pb.set_style(
151 ProgressStyle::default_spinner()
152 .template("{spinner:.green} {msg}")
153 .unwrap(),
154 );
155 pb.set_message("Installing .NET Framework 4.8 via winetricks...");
156 pb.enable_steady_tick(std::time::Duration::from_millis(100));
157
158 match dotnet_builder.install_dotnet().await {
159 Ok(_) => {
160 pb.finish_with_message(".NET Framework installed successfully");
161 }
162 Err(e) => {
163 pb.finish_with_message(".NET Framework installation failed");
164 eprintln!(" Warning: .NET install failed: {}", e);
165 eprintln!(" WeMod may show a '.NET framework' error on startup.");
166 eprintln!(" You can retry manually: winetricks -q dotnet48");
167 }
168 }
169 }
170
171 // Reload prefix after creation
172 prefix_manager.load()?;
173 let prefix = prefix_manager.get("default").unwrap();
174
175 // Install WeMod
176 if !args.skip_wemod {
177 println!("\nInstalling WeMod...");
178
179 let downloader = WemodDownloader::new(&config);
180
181 // Get latest release info
182 let release = downloader.get_latest().await?;
183 if let Some(ref version) = release.version {
184 println!(" Latest version: {}", version);
185 }
186
187 // Download with progress
188 let pb = ProgressBar::new(release.size.unwrap_or(0));
189 pb.set_style(
190 ProgressStyle::default_bar()
191 .template("{spinner:.green} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
192 .unwrap()
193 .progress_chars("#>-"),
194 );
195
196 let installer_path = downloader
197 .download(&release, |downloaded, total| {
198 pb.set_length(total);
199 pb.set_position(downloaded);
200 })
201 .await?;
202
203 pb.finish_with_message("Download complete");
204
205 // Install
206 println!(" Installing WeMod...");
207 let installer = WemodInstaller::new(prefix, proton);
208 installer.install(&installer_path).await?;
209
210 // Update prefix metadata
211 let version = release.version.clone();
212 prefix_manager.update_metadata("default", |p| {
213 p.wemod_installed = true;
214 p.wemod_version = version;
215 })?;
216
217 println!(" WeMod installed successfully");
218 }
219
220 // Save config
221 config.proton.preferred_version = Some(proton.name.clone());
222 match &config_path {
223 Some(path) => config.save_to(path)?,
224 None => config.save()?,
225 }
226
227 println!("\nWANDA initialization complete!");
228 println!("\nNext steps:");
229 println!(" wanda scan - View available games");
230 println!(" wanda launch <game> - Launch a game with WeMod");
231 println!(" wanda doctor - Check for issues");
232
233 Ok(())
234 }
235