From 15b544da5251eb0e40f60b841b4e2cd012b99f7a Mon Sep 17 00:00:00 2001 From: tommy Date: Tue, 3 Jun 2025 01:25:44 -0400 Subject: [PATCH] attempt at support linux (sober) and use the fucking tabs man --- src/main.rs | 353 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 125 deletions(-) diff --git a/src/main.rs b/src/main.rs index c3d3666..be40e71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,148 +3,251 @@ use std::{env, fs, io::{self, stdin, Read, Write}, path::{Path, PathBuf}}; const UNLIMITED_FPS_CAP_NUM: i32 = i32::max_value(); +#[derive(Debug)] +enum Platform { + Windows(PathBuf), + Linux(PathBuf), +} + fn main() -> io::Result<()> { - let roblox_path = find_roblox_path().ok_or_else(|| { - println!("Roblox not found (Die)"); - io::Error::new(io::ErrorKind::NotFound, "Roblox installation not found") - })?; + let platform = find_platform_path().ok_or_else(|| { + println!("Neither Roblox (Windows) nor Sober (Linux) installation found"); + io::Error::new(io::ErrorKind::NotFound, "No supported installation found") + })?; - println!("Found path: {}", roblox_path.display()); - - println!("What FPS would you like to cap the fps to? (0 for no cap at all)"); - io::stdout().flush()?; - - let fps = read_fps_input()?; - let desired_fps = if fps == 0 { UNLIMITED_FPS_CAP_NUM } else { fps }; - - println!("FPS cap will be set to {desired_fps}"); - - let required_settings = json!({ - // Restore original 'alt-enter' behaviour - "FFlagHandleAltEnterFullscreenManually": "False", + match &platform { + Platform::Windows(path) => println!("Found Roblox installation: {}", path.display()), + Platform::Linux(path) => println!("Found Sober installation: {}", path.display()), + } + + println!("What FPS would you like to cap the fps to? (0 for no cap at all)"); + io::stdout().flush()?; + + let fps = read_fps_input()?; + let desired_fps = if fps == 0 { UNLIMITED_FPS_CAP_NUM } else { fps }; + + println!("FPS cap will be set to {desired_fps}"); + + let required_settings = json!({ + // Restore original 'alt-enter' behaviour + "FFlagHandleAltEnterFullscreenManually": "False", - // Disable Roblox's built-in 240 fps hard-coded cap and replace with custom cap - "DFIntTaskSchedulerTargetFps": desired_fps, - "FFlagGameBasicSettingsFramerateCap5": "False", - "FFlagTaskSchedulerLimitTargetFpsTo2402": "False", + // Disable Roblox's built-in 240 fps hard-coded cap and replace with custom cap + "DFIntTaskSchedulerTargetFps": desired_fps, + "FFlagGameBasicSettingsFramerateCap5": "False", + "FFlagTaskSchedulerLimitTargetFpsTo2402": "False", - // Ensure Direct3D11 is the rendering API for optimal performance - "FFlagDebugGraphicsDisableDirect3D11": "False", - "FFlagDebugGraphicsPreferD3D11": "True", + // Ensure Direct3D11 is the rendering API for optimal performance + "FFlagDebugGraphicsDisableDirect3D11": "False", + "FFlagDebugGraphicsPreferD3D11": "True", - // Disable telemetry related FFlags - "FFlagDebugDisableTelemetryEphemeralCounter": "True", - "FFlagDebugDisableTelemetryEphemeralStat": "True", - "FFlagDebugDisableTelemetryEventIngest": "True", - "FFlagDebugDisableTelemetryPoint": "True", - "FFlagDebugDisableTelemetryV2Counter": "True", - "FFlagDebugDisableTelemetryV2Event": "True", - "FFlagDebugDisableTelemetryV2Stat": "True" - }); + // Disable telemetry related FFlags + "FFlagDebugDisableTelemetryEphemeralCounter": "True", + "FFlagDebugDisableTelemetryEphemeralStat": "True", + "FFlagDebugDisableTelemetryEventIngest": "True", + "FFlagDebugDisableTelemetryPoint": "True", + "FFlagDebugDisableTelemetryV2Counter": "True", + "FFlagDebugDisableTelemetryV2Event": "True", + "FFlagDebugDisableTelemetryV2Stat": "True" + }); - update_client_settings(&roblox_path, required_settings)?; - - println!("Press enter to exit"); - let _ = io::stdin().read(&mut [0u8])?; - - Ok(()) + update_settings(platform, required_settings)?; + + println!("Press enter to exit"); + let _ = io::stdin().read(&mut [0u8])?; + + Ok(()) +} + +fn find_platform_path() -> Option { + // Try Windows (Roblox) first + if let Some(roblox_path) = find_roblox_path() { + return Some(Platform::Windows(roblox_path)); + } + + // Try Linux (Sober) + if let Some(sober_path) = find_sober_path() { + return Some(Platform::Linux(sober_path)); + } + + None } fn find_roblox_path() -> Option { - if let Some(local_app_data) = env::var("LOCALAPPDATA").ok() { - let path = PathBuf::from(local_app_data).join("Roblox"); - if path.is_dir() { - return Some(path); - } - } - - // This is here because Roblox installs into Program Files (x86) if you run the installer as administrator (??? lol) - if let Some(program_files) = env::var("ProgramFiles(x86)").ok() { - let path = PathBuf::from(program_files).join("Roblox"); - if path.is_dir() { - return Some(path); - } - } - - None + if let Some(local_app_data) = env::var("LOCALAPPDATA").ok() { + let path = PathBuf::from(local_app_data).join("Roblox"); + if path.is_dir() { + return Some(path); + } + } + + // This is here because Roblox installs into Program Files (x86) if you run the installer as administrator (??? lol) + if let Some(program_files) = env::var("ProgramFiles(x86)").ok() { + let path = PathBuf::from(program_files).join("Roblox"); + if path.is_dir() { + return Some(path); + } + } + + None +} + +fn find_sober_path() -> Option { + if let Some(home) = env::var("HOME").ok() { + let config_path = PathBuf::from(home) + .join(".var") + .join("app") + .join("org.vinegarhq.Sober") + .join("config") + .join("sober") + .join("config.json"); + + if config_path.exists() { + return Some(config_path); + } + } + + None } fn read_fps_input() -> io::Result { - let mut input = String::new(); - stdin().read_line(&mut input)?; - - let input = input.trim(); - - input.parse::().map_err(|_| { - io::Error::new(io::ErrorKind::InvalidInput, "Invalid FPS value") - }) + let mut input = String::new(); + stdin().read_line(&mut input)?; + + let input = input.trim(); + + input.parse::().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidInput, "Invalid FPS value") + }) } -fn update_client_settings(roblox_path: &Path, required_settings: Value) -> io::Result<()> { - let versions_path = roblox_path.join("Versions"); - - for entry in fs::read_dir(versions_path)? { - let entry = entry?; - let path = entry.path(); - let path_str = path.to_string_lossy().to_lowercase(); - - if path_str.contains("version") { - let executable = path.join("RobloxPlayerBeta.exe"); - - if executable.is_file() { - let client_settings_dir = path.join("ClientSettings"); - - if !client_settings_dir.exists() { - fs::create_dir(&client_settings_dir)?; - println!("Created ClientSettings folder in {}", path.display()); - } else { - println!("ClientSettings folder already exists in {}", path.display()); - } - - let settings_file = client_settings_dir.join("ClientAppSettings.json"); - update_settings_file(&settings_file, &required_settings)?; - println!("Updated ClientAppSettings.json in {}", path.display()); - } - } - } - - Ok(()) +fn update_settings(platform: Platform, required_settings: Value) -> io::Result<()> { + match platform { + Platform::Windows(roblox_path) => update_roblox_settings(&roblox_path, required_settings), + Platform::Linux(sober_config_path) => update_sober_settings(&sober_config_path, required_settings), + } } -fn update_settings_file(file_path: &Path, required_settings: &Value) -> io::Result<()> { - // Funky code to edit or create the ClientAppSettings.json file (shoutout claude for the help with the object stuff (json sucks)) - let mut settings = if file_path.exists() { - match fs::read_to_string(file_path) { - Ok(content) if !content.trim().is_empty() => { - match serde_json::from_str::(&content) { - Ok(existing) => existing, - Err(e) => { - println!("Warning: Could not parse existing settings ({}), creating new file", e); - json!({}) - } - } - }, - _ => json!({}) - } - } else { - json!({}) - }; +fn update_roblox_settings(roblox_path: &Path, required_settings: Value) -> io::Result<()> { + let versions_path = roblox_path.join("Versions"); + + for entry in fs::read_dir(versions_path)? { + let entry = entry?; + let path = entry.path(); + let path_str = path.to_string_lossy().to_lowercase(); + + if path_str.contains("version") { + let executable = path.join("RobloxPlayerBeta.exe"); + + if executable.is_file() { + let client_settings_dir = path.join("ClientSettings"); + + if !client_settings_dir.exists() { + fs::create_dir(&client_settings_dir)?; + println!("Created ClientSettings folder in {}", path.display()); + } else { + println!("ClientSettings folder already exists in {}", path.display()); + } + + let settings_file = client_settings_dir.join("ClientAppSettings.json"); + update_roblox_settings_file(&settings_file, &required_settings)?; + println!("Updated ClientAppSettings.json in {}", path.display()); + } + } + } + + Ok(()) +} - if let Some(required_obj) = required_settings.as_object() { - if !settings.is_object() { - settings = json!({}); - } - - if let Some(settings_obj) = settings.as_object_mut() { - for (key, value) in required_obj { - println!("Adding {}: {}", key, value); - settings_obj.insert(key.clone(), value.clone()); - } - } - } +fn update_sober_settings(config_path: &Path, required_settings: Value) -> io::Result<()> { + let mut config = if config_path.exists() { + match fs::read_to_string(config_path) { + Ok(content) if !content.trim().is_empty() => { + match serde_json::from_str::(&content) { + Ok(existing) => existing, + Err(e) => { + println!("Warning: Could not parse existing Sober config ({}), creating new structure", e); + json!({}) + } + } + }, + _ => json!({}) + } + } else { + json!({}) + }; - let formatted = serde_json::to_string_pretty(&settings)?; - fs::write(file_path, formatted)?; + // Ensure config is an object + if !config.is_object() { + config = json!({}); + } - Ok(()) + // Get or create the fflags field + let mut fflags = config.get("fflags").cloned().unwrap_or_else(|| json!({})); + + // Ensure fflags is an object + if !fflags.is_object() { + fflags = json!({}); + } + + // Add our required settings to fflags + if let Some(required_obj) = required_settings.as_object() { + if let Some(fflags_obj) = fflags.as_object_mut() { + for (key, value) in required_obj { + println!("Adding FFlag {}: {}", key, value); + fflags_obj.insert(key.clone(), value.clone()); + } + } + } + + // Update the config with the modified fflags + if let Some(config_obj) = config.as_object_mut() { + config_obj.insert("fflags".to_string(), fflags); + } + + // Write back to file + let formatted = serde_json::to_string_pretty(&config)?; + fs::write(config_path, formatted)?; + + println!("Updated Sober config at {}", config_path.display()); + + Ok(()) +} + +fn update_roblox_settings_file(file_path: &Path, required_settings: &Value) -> io::Result<()> { + // Funky code to edit or create the ClientAppSettings.json file (shoutout claude for the help with the object stuff (json sucks)) + let mut settings = if file_path.exists() { + match fs::read_to_string(file_path) { + Ok(content) if !content.trim().is_empty() => { + match serde_json::from_str::(&content) { + Ok(existing) => existing, + Err(e) => { + println!("Warning: Could not parse existing settings ({}), creating new file", e); + json!({}) + } + } + }, + _ => json!({}) + } + } else { + json!({}) + }; + + if let Some(required_obj) = required_settings.as_object() { + if !settings.is_object() { + settings = json!({}); + } + + if let Some(settings_obj) = settings.as_object_mut() { + for (key, value) in required_obj { + println!("Adding {}: {}", key, value); + settings_obj.insert(key.clone(), value.clone()); + } + } + } + + let formatted = serde_json::to_string_pretty(&settings)?; + fs::write(file_path, formatted)?; + + Ok(()) } \ No newline at end of file