diff --git a/crates/molotov-cli/src/commands.rs b/crates/molotov-cli/src/commands.rs index 2624341..556f8b8 100644 --- a/crates/molotov-cli/src/commands.rs +++ b/crates/molotov-cli/src/commands.rs @@ -16,6 +16,8 @@ pub enum Commands { place_id: Option, #[arg(long)] safe_mode: bool, + #[arg(short, long)] + region: Option, }, /// Check for and download the latest Roblox version Update, diff --git a/crates/molotov-cli/src/lib.rs b/crates/molotov-cli/src/lib.rs index 1874129..f72d4ec 100644 --- a/crates/molotov-cli/src/lib.rs +++ b/crates/molotov-cli/src/lib.rs @@ -3,8 +3,12 @@ pub mod commands; use clap::Parser; use commands::{Cli, Commands}; use molotov_bootstrap::cdn::CdnClient; +use molotov_bootstrap::cleaner::Cleaner; use molotov_bootstrap::fflags::FastFlagManager; +use molotov_bootstrap::launcher::{LaunchOptions, Launcher}; +use molotov_bootstrap::region::ServerRegion; use molotov_bootstrap::sandbox::SandboxRunner; +use std::path::PathBuf; pub fn run(args: &[String]) { let mut full_args = vec!["molotov".to_string()]; @@ -18,12 +22,45 @@ pub fn run(args: &[String]) { } }; + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + let base_dir = PathBuf::from(&home).join(".local/share/molotov"); + let downloads_dir = PathBuf::from(&home).join(".cache/molotov/downloads"); + let versions_dir = base_dir.join("versions"); + match cli.command { - Some(Commands::Launch { place_id, safe_mode }) => { - println!("๐Ÿš€ Preparing Molotov launch (Place: {:?}, Safe Mode: {})...", place_id, safe_mode); + Some(Commands::Launch { place_id, safe_mode, region }) => { + let s_region = region + .as_deref() + .map(ServerRegion::from_code) + .unwrap_or(ServerRegion::Auto); + + println!("๐Ÿš€ Preparing Molotov launch..."); + println!(" โ€ข Target Place ID: {:?}", place_id); + println!(" โ€ข Safe Mode: {}", safe_mode); + println!(" โ€ข Preferred Server Region: {}", s_region.display_name()); + if let Err(e) = SandboxRunner::check_bwrap_installed() { eprintln!("โš ๏ธ Warning: {}", e); } + + let launcher = Launcher::new(base_dir); + let options = LaunchOptions { + place_id, + safe_mode, + region: s_region, + instance_id: "default".to_string(), + }; + + match launcher.prepare_launch(options) { + Ok(plan) => { + println!("โœ… Launch plan prepared successfully."); + println!(" โ€ข FastFlags configured at: {}", plan.fflags_path.display()); + println!(" โ€ข Sandbox isolation: Instance '{}'", plan.sandbox_config.instance_id); + } + Err(e) => { + eprintln!("โŒ Failed to prepare launch plan: {}", e); + } + } } Some(Commands::Update) => { println!("๐Ÿ”„ Checking Roblox CDN for client updates..."); @@ -62,6 +99,18 @@ pub fn run(args: &[String]) { } Some(Commands::Clean) => { println!("๐Ÿงน Cleaning old versions and temporary cache..."); + let cleaner = Cleaner::new(versions_dir, downloads_dir); + match cleaner.clean_stale_data("active") { + Ok(report) => { + println!("โœ… Cleaning complete:"); + println!(" โ€ข Purged old version directories: {}", report.purged_versions.len()); + println!(" โ€ข Purged leftover download fragments: {}", report.purged_parts_count); + println!(" โ€ข Total disk space reclaimed: {:.2} MB", (report.reclaimed_bytes as f64) / (1024.0 * 1024.0)); + } + Err(e) => { + eprintln!("โŒ Cleaning failed: {}", e); + } + } } Some(Commands::Diagnostics) => { println!("๐Ÿฉบ System diagnostics: checking bubblewrap sandbox status..."); diff --git a/crates/molotov-cli/tests/cli_test.rs b/crates/molotov-cli/tests/cli_test.rs index 6ae7db4..db3fa70 100644 --- a/crates/molotov-cli/tests/cli_test.rs +++ b/crates/molotov-cli/tests/cli_test.rs @@ -8,6 +8,12 @@ fn test_cli_parse_launch_place() { assert!(parsed.is_ok()); } +#[test] +fn test_cli_parse_region_flag() { + let parsed = Cli::try_parse_from(["molotov", "launch", "--region", "eu"]); + assert!(parsed.is_ok()); +} + #[test] fn test_cli_parse_clean_command() { let parsed = Cli::try_parse_from(["molotov", "clean"]); diff --git a/crates/molotov-gui/src/app.rs b/crates/molotov-gui/src/app.rs index 46591b8..0c0e0a0 100644 --- a/crates/molotov-gui/src/app.rs +++ b/crates/molotov-gui/src/app.rs @@ -1,5 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later use eframe::egui; +use molotov_bootstrap::cleaner::Cleaner; +use molotov_bootstrap::launcher::{LaunchOptions, Launcher}; +use molotov_bootstrap::region::ServerRegion; +use std::path::PathBuf; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tab { @@ -16,6 +20,9 @@ pub struct MolotovApp { pub safe_mode: bool, pub fps_cap: i64, pub vram_skip_mips: bool, + pub region: ServerRegion, + pub discord_rpc: bool, + pub status_message: String, } impl Default for MolotovApp { @@ -25,6 +32,9 @@ impl Default for MolotovApp { safe_mode: false, fps_cap: 144, vram_skip_mips: false, + region: ServerRegion::Auto, + discord_rpc: true, + status_message: "Ready to launch".to_string(), } } } @@ -40,6 +50,14 @@ impl MolotovApp { Tab::Maintenance => "Maintenance", } } + + fn get_paths() -> (PathBuf, PathBuf, PathBuf) { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + let base_dir = PathBuf::from(&home).join(".local/share/molotov"); + let downloads_dir = PathBuf::from(&home).join(".cache/molotov/downloads"); + let versions_dir = base_dir.join("versions"); + (base_dir, downloads_dir, versions_dir) + } } impl eframe::App for MolotovApp { @@ -58,9 +76,44 @@ impl eframe::App for MolotovApp { egui::CentralPanel::default().show(ctx, |ui| match self.current_tab { Tab::Play => { ui.heading("Molotov Roblox Runtime"); + ui.label(format!("Status: {}", self.status_message)); + ui.separator(); + ui.checkbox(&mut self.safe_mode, "Safe Mode (Minimal Flags)"); + + ui.horizontal(|ui| { + ui.label("Preferred Server Region:"); + egui::ComboBox::from_id_salt("region_combo") + .selected_text(self.region.display_name()) + .show_ui(ui, |ui| { + ui.selectable_value(&mut self.region, ServerRegion::Auto, "Auto / Default"); + ui.selectable_value(&mut self.region, ServerRegion::USEast, "US East (Virginia)"); + ui.selectable_value(&mut self.region, ServerRegion::USWest, "US West (California)"); + ui.selectable_value(&mut self.region, ServerRegion::Europe, "Europe (Frankfurt / London)"); + ui.selectable_value(&mut self.region, ServerRegion::Asia, "Asia (Tokyo / Singapore)"); + ui.selectable_value(&mut self.region, ServerRegion::Australia, "Australia (Sydney)"); + }); + }); + + ui.add_space(10.0); if ui.button("๐Ÿš€ Launch Roblox").clicked() { - println!("Launch clicked!"); + let (base_dir, _, _) = Self::get_paths(); + let launcher = Launcher::new(base_dir); + let options = LaunchOptions { + place_id: None, + safe_mode: self.safe_mode, + region: self.region, + instance_id: "default".to_string(), + }; + + match launcher.prepare_launch(options) { + Ok(plan) => { + self.status_message = format!("Launch plan prepared! Safe Mode: {}", plan.is_safe_mode); + } + Err(e) => { + self.status_message = format!("Launch preparation failed: {}", e); + } + } } } Tab::FastFlags => { @@ -82,8 +135,21 @@ impl eframe::App for MolotovApp { } Tab::Maintenance => { ui.heading("Maintenance & Cleaning"); + ui.checkbox(&mut self.discord_rpc, "Enable Discord Rich Presence (RPC)"); + + ui.add_space(10.0); if ui.button("Clean Stale Versions").clicked() { - println!("Clean clicked!"); + let (_, downloads_dir, versions_dir) = Self::get_paths(); + let cleaner = Cleaner::new(versions_dir, downloads_dir); + match cleaner.clean_stale_data("active") { + Ok(report) => { + let mb = (report.reclaimed_bytes as f64) / (1024.0 * 1024.0); + self.status_message = format!("Cleaned {} old versions, reclaimed {:.2} MB", report.purged_versions.len(), mb); + } + Err(e) => { + self.status_message = format!("Clean failed: {}", e); + } + } } } });