diff --git a/crates/molotov-bootstrap/src/fflags.rs b/crates/molotov-bootstrap/src/fflags.rs new file mode 100644 index 0000000..9279cf3 --- /dev/null +++ b/crates/molotov-bootstrap/src/fflags.rs @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use crate::error::MolotovError; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::path::Path; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum FlagValue { + Boolean(bool), + Integer(i64), + Text(String), +} + +impl From for FlagValue { + fn from(v: bool) -> Self { + FlagValue::Boolean(v) + } +} + +impl From for FlagValue { + fn from(v: i64) -> Self { + FlagValue::Integer(v) + } +} + +impl From for FlagValue { + fn from(v: i32) -> Self { + FlagValue::Integer(v as i64) + } +} + +impl From<&str> for FlagValue { + fn from(v: &str) -> Self { + FlagValue::Text(v.to_string()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PresetProfile { + Default, + LowVram, + CompetitiveUncapped, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct FastFlagProfile { + pub name: String, + pub flags: HashMap, +} + +impl FastFlagProfile { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + flags: HashMap::new(), + } + } + + pub fn set(&mut self, key: &str, val: FlagValue) { + self.flags.insert(key.to_string(), val); + } + + pub fn get_int(&self, key: &str) -> Option { + match self.flags.get(key) { + Some(FlagValue::Integer(v)) => Some(*v), + _ => None, + } + } + + pub fn get_bool(&self, key: &str) -> Option { + match self.flags.get(key) { + Some(FlagValue::Boolean(v)) => Some(*v), + _ => None, + } + } +} + +pub struct FastFlagManager { + active_profile: FastFlagProfile, + allowlist: HashSet, +} + +impl Default for FastFlagManager { + fn default() -> Self { + Self::new() + } +} + +impl FastFlagManager { + pub fn new() -> Self { + let mut allowlist = HashSet::new(); + // Seed standard safe FastFlags + allowlist.insert("FIntDebugTextureManagerSkipMips".to_string()); + allowlist.insert("DFIntTextureCompositorActiveJobs".to_string()); + allowlist.insert("DFIntTaskSchedulerTargetFps".to_string()); + allowlist.insert("FFlagDebugGraphicsDisableDirect3D11".to_string()); + allowlist.insert("DFFlagDebugPauseVoxelizer".to_string()); + + Self { + active_profile: FastFlagProfile::new("Default"), + allowlist, + } + } + + pub fn active_profile(&self) -> &FastFlagProfile { + &self.active_profile + } + + pub fn active_profile_mut(&mut self) -> &mut FastFlagProfile { + &mut self.active_profile + } + + pub fn set_flag(&mut self, key: &str, val: FlagValue) { + self.active_profile.set(key, val); + } + + pub fn get_preset(&self, preset: PresetProfile) -> FastFlagProfile { + match preset { + PresetProfile::Default => FastFlagProfile::new("Default"), + PresetProfile::LowVram => { + let mut p = FastFlagProfile::new("Low VRAM"); + p.set("FIntDebugTextureManagerSkipMips", 1.into()); + p.set("DFIntTextureCompositorActiveJobs", 2.into()); + p + } + PresetProfile::CompetitiveUncapped => { + let mut p = FastFlagProfile::new("Competitive Uncapped"); + p.set("DFIntTaskSchedulerTargetFps", 240.into()); + p + } + } + } + + pub fn apply_preset(&mut self, preset: PresetProfile) { + self.active_profile = self.get_preset(preset); + } + + pub fn validate_against_allowlist(&self) -> Vec { + let mut unverified = Vec::new(); + for key in self.active_profile.flags.keys() { + if !self.allowlist.contains(key) { + unverified.push(key.clone()); + } + } + unverified.sort(); + unverified + } + + pub fn export_client_app_settings(&self, target_path: &Path) -> Result<(), MolotovError> { + if let Some(parent) = target_path.parent() { + std::fs::create_dir_all(parent)?; + } + let json = serde_json::to_string_pretty(&self.active_profile.flags) + .map_err(|e| MolotovError::FastFlag(e.to_string()))?; + std::fs::write(target_path, json)?; + Ok(()) + } +} diff --git a/crates/molotov-bootstrap/src/lib.rs b/crates/molotov-bootstrap/src/lib.rs index 3434e32..dec9401 100644 --- a/crates/molotov-bootstrap/src/lib.rs +++ b/crates/molotov-bootstrap/src/lib.rs @@ -2,3 +2,4 @@ pub mod apk; pub mod cdn; pub mod error; +pub mod fflags; diff --git a/crates/molotov-bootstrap/tests/fflags_test.rs b/crates/molotov-bootstrap/tests/fflags_test.rs new file mode 100644 index 0000000..666b0d1 --- /dev/null +++ b/crates/molotov-bootstrap/tests/fflags_test.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_bootstrap::fflags::{FastFlagManager, PresetProfile}; + +#[test] +fn test_low_vram_preset_contains_mip_skip() { + let manager = FastFlagManager::new(); + let profile = manager.get_preset(PresetProfile::LowVram); + + assert_eq!( + profile.get_int("FIntDebugTextureManagerSkipMips"), + Some(1), + "Low VRAM preset must set FIntDebugTextureManagerSkipMips to 1" + ); +} + +#[test] +fn test_allowlist_validation_warning() { + let mut manager = FastFlagManager::new(); + manager.set_flag("FIntDebugTextureManagerSkipMips", 1.into()); + manager.set_flag("FUnknownArbitraryFlagExploit", true.into()); + + let warnings = manager.validate_against_allowlist(); + assert_eq!(warnings.len(), 1); + assert_eq!(warnings[0], "FUnknownArbitraryFlagExploit"); +}