rgpre/import/
menu_settings.rs1use std::collections::BTreeMap;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct MenuSettings {
7 pub subtitles: bool,
8 pub movie_interlace: bool,
9 pub voice: bool,
10 pub sound_volume: u8,
15 pub music_volume: u8,
16 pub repeat_ticks: u32,
18}
19
20impl MenuSettings {
21 pub fn parse(content: &str) -> Result<Self, String> {
23 let mut section = String::new();
24 let mut fields = BTreeMap::new();
25 for line in content.lines() {
26 let line = line.split(';').next().unwrap_or_default().trim();
27 if line.starts_with('[') && line.ends_with(']') {
28 section = line[1..line.len() - 1].to_ascii_lowercase();
29 } else if let Some((key, value)) = line.split_once('=') {
30 fields
31 .entry((section.clone(), key.trim().to_ascii_lowercase()))
32 .or_insert(value.trim());
33 }
34 }
35 let number = |section: &str, key: &str, max: u32| -> Result<u32, String> {
36 let context = format!("SYSTEM.INI [{section}] {key}");
37 let value = fields
38 .get(&(section.into(), key.into()))
39 .ok_or_else(|| format!("{context}: missing value"))?;
40 value
41 .parse::<u32>()
42 .ok()
43 .filter(|n| *n <= max)
44 .ok_or_else(|| format!("{context}: expected 0..{max}, got {value:?}"))
45 };
46 let repeat_ticks = number("dialog", "menu_traverse_delay", u32::MAX)?;
47 if repeat_ticks == 0 {
48 return Err("SYSTEM.INI [dialog] menu_traverse_delay: must be positive".into());
49 }
50 Ok(Self {
51 subtitles: number("dialog", "dialog_print_text", 1)? != 0,
52 movie_interlace: number("screen", "smk_interlace", 1)? != 0,
53 voice: number("dialog", "dialog_use_speech", 1)? != 0,
54 sound_volume: (f64::from(number("system", "volume", 256)?) / 12.8).trunc() as u8,
55 music_volume: (f64::from(number("system", "redbook_volume", 256)?) / 12.8).trunc()
56 as u8,
57 repeat_ticks,
58 })
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 const CONFIG: &str = "[dialog]\nmenu_traverse_delay=4\ndialog_print_text=1\ndialog_use_speech=0\n[screen]\nsmk_interlace=1\n[system]\nvolume=255\nredbook_volume=200\n";
66 #[test]
67 fn installed_preferences_use_menu_units_and_sections() {
68 assert_eq!(
69 MenuSettings::parse(CONFIG).unwrap(),
70 MenuSettings {
71 subtitles: true,
72 movie_interlace: true,
73 voice: false,
74 sound_volume: 19,
75 music_volume: 15,
76 repeat_ticks: 4,
77 }
78 );
79 for text in [
80 CONFIG.replace("volume=255", "volume=257"),
81 CONFIG.replace("delay=4", "delay=0"),
82 CONFIG.replace("text=1", "text=bad"),
83 ] {
84 assert!(
85 MenuSettings::parse(&text)
86 .unwrap_err()
87 .contains("SYSTEM.INI")
88 );
89 }
90 }
91}