attempt at support linux (sober) and use the fucking tabs man
This commit is contained in:
parent
fd3863c0d4
commit
15b544da52
353
src/main.rs
353
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<Platform> {
|
||||
// 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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<i32> {
|
||||
let mut input = String::new();
|
||||
stdin().read_line(&mut input)?;
|
||||
|
||||
let input = input.trim();
|
||||
|
||||
input.parse::<i32>().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::<i32>().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::<Value>(&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::<Value>(&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::<Value>(&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(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user