1use clap::Parser;
2use clap::Subcommand;
3use clap::ValueEnum;
4
5#[derive(Parser, Debug)]
6#[command(
7 name = "steamroom",
8 about = "Steam depot downloader",
9 after_help = "Set DD_COMPAT=1 for flat-argument compatibility with the original DepotDownloader."
10)]
11pub struct Cli {
12 #[command(subcommand)]
13 pub command: Command,
14
15 #[command(flatten)]
16 pub auth: AuthOptions,
17
18 #[arg(long, global = true)]
20 pub debug: bool,
21
22 #[arg(long, global = true)]
24 pub raw_errors: bool,
25
26 #[arg(long, global = true)]
28 pub cell_id: Option<u32>,
29
30 #[arg(long, global = true)]
32 pub capture: Option<std::path::PathBuf>,
33
34 #[arg(long, global = true)]
36 pub no_progress: bool,
37
38 #[arg(short, long, global = true)]
40 pub quiet: bool,
41
42 #[arg(long, env = "STEAMROOM_NON_INTERACTIVE", global = true)]
45 pub non_interactive: bool,
46
47 #[arg(long = "use-daemon", global = true)]
51 pub use_daemon: bool,
52
53 #[arg(long, hide = true)]
56 pub daemon_resume: Option<String>,
57
58 #[arg(long, global = true)]
61 pub priority: bool,
62
63 #[arg(long, global = true)]
66 pub detach: bool,
67}
68
69#[derive(Parser, Debug)]
75#[command(name = "steamroom", about = "Steam depot downloader (DD_COMPAT mode)")]
76pub struct CompatCli {
77 #[arg(long = "app")]
78 pub app_id: Option<u32>,
79 #[arg(long = "depot")]
80 pub depot_id: Option<u32>,
81 #[arg(long = "manifest")]
82 pub manifest_id: Option<u64>,
83 #[arg(long = "username")]
84 pub username: Option<String>,
85 #[arg(long = "password")]
86 pub password: Option<String>,
87 #[arg(long = "dir")]
88 pub output: Option<std::path::PathBuf>,
89 #[arg(long = "branch")]
90 pub branch: Option<String>,
91 #[arg(long = "betapassword")]
92 pub beta_password: Option<String>,
93 #[arg(long)]
94 pub qr: bool,
95 #[arg(long = "remember-password")]
96 pub remember_password: bool,
97 #[arg(long = "filelist")]
98 pub filelist: Option<std::path::PathBuf>,
99 #[arg(long = "regex")]
100 pub file_regex: Option<String>,
101 #[arg(long = "validate")]
102 pub verify: bool,
103 #[arg(long)]
104 pub os: Option<String>,
105 #[arg(long)]
106 pub arch: Option<String>,
107 #[arg(long)]
108 pub language: Option<String>,
109 #[arg(long = "max-downloads")]
110 pub max_downloads: Option<usize>,
111 #[arg(long = "cellid")]
112 pub cell_id: Option<u32>,
113 #[arg(long)]
114 pub debug: bool,
115 #[arg(long = "device-name", env = "DD_DEVICE_NAME")]
116 pub device_name: Option<String>,
117}
118
119impl CompatCli {
120 pub fn into_cli(self) -> Cli {
121 let app = self.app_id.unwrap_or(0);
122 Cli {
123 command: Command::Download(DownloadArgs {
124 app,
125 depot: self.depot_id,
126 manifest: self.manifest_id,
127 filelist: self.filelist,
128 file_regex: self.file_regex,
129 output: self.output,
130 verify: self.verify,
131 os: self.os,
132 arch: self.arch,
133 language: self.language,
134 login_id: None,
135 all_platforms: false,
136 all_architectures: false,
137 all_languages: false,
138 lancache: false,
139 max_downloads: self.max_downloads,
140 branch: self.branch,
141 branch_password: self.beta_password,
142 local_keys: false,
143 non_atomic: false,
144 save_manifests: false,
145 bytes: false,
146 }),
147 auth: AuthOptions {
148 username: self.username,
149 password: self.password,
150 qr: self.qr,
151 use_steam_token: false,
152 remember_password: self.remember_password,
153 device_name: self.device_name,
154 },
155 debug: self.debug,
156 raw_errors: false,
157 cell_id: self.cell_id,
158 capture: None,
159 no_progress: false,
160 quiet: false,
161 non_interactive: false,
162 use_daemon: false,
163 daemon_resume: None,
164 priority: false,
165 detach: false,
166 }
167 }
168}
169
170#[derive(Parser, Debug)]
171pub struct AuthOptions {
172 #[arg(short, long, env = "STEAM_USER", global = true)]
174 pub username: Option<String>,
175
176 #[arg(short, long, env = "STEAM_PASS", global = true)]
178 pub password: Option<String>,
179
180 #[arg(long, global = true)]
182 pub qr: bool,
183
184 #[arg(long, global = true)]
186 pub use_steam_token: bool,
187
188 #[arg(long, global = true)]
190 pub remember_password: bool,
191
192 #[arg(long, env = "DD_DEVICE_NAME", global = true)]
194 pub device_name: Option<String>,
195}
196
197#[derive(Subcommand, Debug)]
198pub enum Command {
199 Daemon(DaemonArgs),
202 Diff(DiffArgs),
204 Download(DownloadArgs),
206 Files(FilesArgs),
208 Info(InfoArgs),
210 LocalInfo(LocalInfoArgs),
212 Manifests(ManifestsArgs),
214 Packages(PackagesArgs),
216 SaveManifest(SaveManifestArgs),
218 Workshop(WorkshopArgs),
220}
221
222#[derive(Parser, Debug)]
223pub struct DaemonArgs {
224 #[command(subcommand)]
225 pub command: DaemonSub,
226}
227
228#[derive(Subcommand, Debug)]
229pub enum DaemonSub {
230 Start,
234 Stop {
236 #[arg(long)]
238 force: bool,
239 },
240 Status {
242 #[arg(long)]
245 text: bool,
246 #[arg(long, value_enum)]
249 format: Option<OutputFormat>,
250 },
251 Info,
254 Attach {
261 job_id: u64,
264 },
265}
266
267#[derive(Parser, Debug)]
268pub struct DownloadArgs {
269 #[arg(long)]
271 pub app: u32,
272 #[arg(long)]
274 pub depot: Option<u32>,
275 #[arg(long)]
277 pub manifest: Option<u64>,
278 #[arg(long)]
280 pub filelist: Option<std::path::PathBuf>,
281 #[arg(long)]
283 pub file_regex: Option<String>,
284 #[arg(long, short)]
286 pub output: Option<std::path::PathBuf>,
287 #[arg(long)]
289 pub verify: bool,
290 #[arg(long)]
292 pub os: Option<String>,
293 #[arg(long)]
295 pub arch: Option<String>,
296 #[arg(long)]
298 pub language: Option<String>,
299 #[arg(long)]
301 pub login_id: Option<u32>,
302 #[arg(long)]
304 pub all_platforms: bool,
305 #[arg(long)]
307 pub all_architectures: bool,
308 #[arg(long)]
310 pub all_languages: bool,
311 #[arg(long)]
313 pub lancache: bool,
314 #[arg(long)]
316 pub max_downloads: Option<usize>,
317 #[arg(long)]
319 pub branch: Option<String>,
320 #[arg(long)]
322 pub branch_password: Option<String>,
323 #[arg(long)]
325 pub local_keys: bool,
326 #[arg(long)]
328 pub non_atomic: bool,
329 #[arg(long)]
331 pub save_manifests: bool,
332 #[arg(long)]
334 pub bytes: bool,
335}
336
337#[derive(Parser, Debug)]
338pub struct FilesArgs {
339 #[arg(long)]
341 pub app: Option<u32>,
342 #[arg(long)]
344 pub depot: Option<u32>,
345 #[arg(long)]
347 pub manifest: Option<u64>,
348 #[arg(long, value_name = "PATH")]
350 pub manifest_file: Option<std::path::PathBuf>,
351 #[arg(long, value_name = "HEX")]
353 pub depot_key: Option<String>,
354 #[arg(long)]
356 pub branch: Option<String>,
357 #[arg(long)]
359 pub branch_password: Option<String>,
360 #[arg(long)]
362 pub os: Option<String>,
363 #[arg(long, value_enum)]
365 pub format: Option<OutputFormat>,
366 #[arg(long)]
368 pub raw: bool,
369 #[arg(long)]
371 pub bytes: bool,
372}
373
374#[derive(Parser, Debug)]
375pub struct LocalInfoArgs {
376 #[arg(long, value_enum)]
378 pub format: Option<OutputFormat>,
379 #[arg(long)]
381 pub user: Option<String>,
382 #[arg(long)]
384 pub users: bool,
385}
386
387#[derive(Parser, Debug)]
388pub struct SaveManifestArgs {
389 #[arg(long)]
391 pub app: u32,
392 #[arg(long)]
394 pub depot: u32,
395 #[arg(long)]
397 pub manifest: Option<u64>,
398 #[arg(long)]
400 pub branch: Option<String>,
401 #[arg(long, short)]
403 pub output: std::path::PathBuf,
404}
405
406#[derive(Parser, Debug)]
407pub struct InfoArgs {
408 #[arg(long)]
410 pub app: u32,
411 #[arg(long, value_enum)]
413 pub format: Option<OutputFormat>,
414 #[arg(long)]
416 pub os: Option<String>,
417 #[arg(long)]
419 pub show_all: bool,
420}
421
422#[derive(Parser, Debug)]
423pub struct ManifestsArgs {
424 #[arg(long)]
426 pub app: u32,
427 #[arg(long)]
429 pub branch: Option<String>,
430 #[arg(long)]
432 pub branch_password: Option<String>,
433 #[arg(long, value_enum)]
435 pub format: Option<OutputFormat>,
436}
437
438#[derive(Parser, Debug)]
439pub struct WorkshopArgs {
440 #[arg(long)]
442 pub app: u32,
443 #[arg(long)]
445 pub item: u64,
446 #[arg(long, short)]
448 pub output: Option<std::path::PathBuf>,
449}
450
451#[derive(Parser, Debug)]
452pub struct DiffArgs {
453 #[arg(long)]
455 pub app: u32,
456 #[arg(long)]
458 pub depot: u32,
459 #[arg(long)]
461 pub from: u64,
462 #[arg(long)]
464 pub to: u64,
465 #[arg(long)]
467 pub branch: Option<String>,
468 #[arg(long, value_enum)]
470 pub format: Option<OutputFormat>,
471}
472
473#[derive(Parser, Debug)]
474pub struct PackagesArgs {
475 #[arg(value_name = "PACKAGE", required = true, num_args = 1..)]
477 pub packages: Vec<u32>,
478 #[arg(long, value_enum)]
480 pub format: Option<OutputFormat>,
481}
482
483#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
484pub enum OutputFormat {
485 Table,
486 Json,
487 Plain,
488}
489
490use crate::daemon::proto::Request;
491use crate::errors::CliError;
492
493impl Cli {
494 pub fn into_rpc_request(self) -> Result<Request, CliError> {
499 let auth = &self.auth;
500 let has_auth = auth.username.is_some()
501 || auth.password.is_some()
502 || auth.qr
503 || auth.use_steam_token
504 || auth.remember_password
505 || auth.device_name.is_some();
506 if has_auth {
507 eprintln!(
508 "warning: auth flags are ignored under --use-daemon; \
509 the daemon serves the account it was started with"
510 );
511 }
512 if self.capture.is_some() {
513 eprintln!("warning: --capture is ignored under --use-daemon");
514 }
515
516 let priority = self.priority;
517 match self.command {
518 Command::Download(a) => Ok(Request::Download {
519 args: crate::daemon::proto::DownloadParams::from(a),
520 priority,
521 }),
522 Command::Info(a) => Ok(Request::Info {
523 args: crate::daemon::proto::InfoParams::from(a),
524 priority,
525 }),
526 Command::Files(a) => Ok(Request::Files {
527 args: crate::daemon::proto::FilesParams::from(a),
528 priority,
529 }),
530 Command::Manifests(a) => Ok(Request::Manifests {
531 args: crate::daemon::proto::ManifestsParams::from(a),
532 priority,
533 }),
534 Command::Diff(a) => Ok(Request::Diff {
535 args: crate::daemon::proto::DiffParams::from(a),
536 priority,
537 }),
538 Command::Packages(a) => Ok(Request::Packages {
539 args: crate::daemon::proto::PackagesParams::from(a),
540 priority,
541 }),
542 Command::SaveManifest(a) => Ok(Request::SaveManifest {
543 args: crate::daemon::proto::SaveManifestParams::from(a),
544 priority,
545 }),
546 Command::Workshop(a) => Ok(Request::Workshop {
547 args: crate::daemon::proto::WorkshopParams::from(a),
548 priority,
549 }),
550 Command::LocalInfo(a) => Ok(Request::LocalInfo {
551 args: crate::daemon::proto::LocalInfoParams::from(a),
552 priority,
553 }),
554 Command::Daemon(_) => Err(CliError::DaemonRejectedFlag("daemon subcommand")),
555 }
556 }
557
558 pub fn validate(&self) -> Result<(), CliError> {
560 if self.priority && !self.use_daemon {
561 return Err(CliError::PriorityWithoutDaemon);
562 }
563 if self.detach && !self.use_daemon {
564 return Err(CliError::DetachWithoutDaemon);
565 }
566 if self.use_daemon
569 && matches!(
570 self.command,
571 Command::Daemon(DaemonArgs {
572 command: DaemonSub::Start
573 })
574 )
575 {
576 return Err(CliError::DaemonModeConflict);
577 }
578 Ok(())
579 }
580}