feat(bootstrap): implement FastFlag manager, presets, and allowlist validation
This commit is contained in:
parent
44a858ace2
commit
72dcee25a8
3 changed files with 185 additions and 0 deletions
159
crates/molotov-bootstrap/src/fflags.rs
Normal file
159
crates/molotov-bootstrap/src/fflags.rs
Normal file
|
|
@ -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<bool> for FlagValue {
|
||||
fn from(v: bool) -> Self {
|
||||
FlagValue::Boolean(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for FlagValue {
|
||||
fn from(v: i64) -> Self {
|
||||
FlagValue::Integer(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> 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<String, FlagValue>,
|
||||
}
|
||||
|
||||
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<i64> {
|
||||
match self.flags.get(key) {
|
||||
Some(FlagValue::Integer(v)) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, key: &str) -> Option<bool> {
|
||||
match self.flags.get(key) {
|
||||
Some(FlagValue::Boolean(v)) => Some(*v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FastFlagManager {
|
||||
active_profile: FastFlagProfile,
|
||||
allowlist: HashSet<String>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -2,3 +2,4 @@
|
|||
pub mod apk;
|
||||
pub mod cdn;
|
||||
pub mod error;
|
||||
pub mod fflags;
|
||||
|
|
|
|||
25
crates/molotov-bootstrap/tests/fflags_test.rs
Normal file
25
crates/molotov-bootstrap/tests/fflags_test.rs
Normal file
|
|
@ -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");
|
||||
}
|
||||
Reference in a new issue