Cactus/config/
mod.rs

1//! This module is the interface between the server.properties file. Querying for server settings.
2use core::fmt;
3// !TODO generator_settings
4// !Todo text-filtering-config
5// use dot_properties::{read_properties, Properties};
6use read_properties::Properties;
7use std::fs::File;
8use std::io::BufReader;
9use std::io::{Error, ErrorKind};
10use std::net::Ipv4Addr;
11use std::path::Path;
12use std::str::FromStr;
13pub mod read_properties;
14//use std::sync::Arc;
15
16/// Function to get a `Properties` object to which the caller can then query keys.
17///
18/// # Example
19/// ```rust
20/// let config_file = config::read(Path::new(consts::filepaths::PROPERTIES))
21///     .expect("Error reading server.properties file");
22///
23/// let difficulty = config_file.get_property("difficulty").unwrap();
24/// let max_players = config_file.get_property("max_players").unwrap();
25///
26/// // Note that `get_property()` returns a `Result<&'_ str, PropertyNotFoundError<'a>>`
27/// // So everything's a string slice.
28/// println!("{difficulty}");
29/// println!("{max_players}");
30/// ```
31///
32#[derive(Debug)]
33pub enum Difficulty {
34    Easy,
35    Normal,
36    Hard,
37}
38
39#[derive(Debug)]
40pub enum Gamemode {
41    Adventure,
42    Survival,
43    Creative,
44    Spectator,
45}
46
47#[derive(Debug)]
48pub enum WorldPreset {
49    Normal,
50    Flat,
51    LargeBiomes,
52    Amplified,
53    SingleBiomeSurface,
54}
55
56// TODO: Maybe make Settings a singleton
57
58#[derive(Debug)]
59pub struct Settings {
60    pub enable_jmx_monitoring: bool,
61    pub rcon_port: u16,
62    pub level_seed: Option<i64>,
63    pub gamemode: Gamemode,
64    pub enable_command_block: bool,
65    pub enable_query: bool,
66    pub enforce_secure_profile: bool,
67    pub level_name: Option<String>,
68    pub motd: Option<String>,
69    pub query_port: u16,
70    pub pvp: bool,
71    pub generate_structures: bool,
72    pub max_chained_neighbor_updates: Option<i32>,
73    pub difficulty: Difficulty,
74    pub network_compression_threshold: i32,
75    pub max_tick_time: i64,
76    pub require_resource_pack: bool,
77    pub use_native_transport: bool,
78    pub max_players: u32,
79    pub online_mode: bool,
80    pub enable_status: bool,
81    pub allow_flight: bool,
82    pub initial_disabled_packs: Option<String>,
83    pub broadcast_rcon_to_ops: bool,
84    pub view_distance: u8,
85    pub server_ip: Option<Ipv4Addr>,
86    pub resource_pack_prompt: Option<String>,
87    pub allow_nether: bool,
88    pub server_port: u16,
89    pub enable_rcon: bool,
90    pub sync_chunk_writes: bool,
91    pub op_permission_level: u8,
92    pub prevent_proxy_connections: bool,
93    pub hide_online_players: bool,
94    pub resource_pack: Option<String>,
95    pub entity_broadcast_range_percentage: u8,
96    pub simulation_distance: u8,
97    pub rcon_password: Option<String>,
98    pub player_idle_timeout: i32,
99    pub force_gamemode: bool,
100    pub rate_limit: u32,
101    pub hardcore: bool,
102    pub white_list: bool,
103    pub broadcast_console_to_ops: bool,
104    pub spawn_npcs: bool,
105    pub spawn_animals: bool,
106    pub log_ips: bool,
107    pub function_permission_level: u8,
108    pub initial_enabled_packs: String,
109    pub level_type: WorldPreset,
110    pub spawn_monsters: bool,
111    pub enforce_whitelist: bool,
112    pub spawn_protection: u16,
113    pub resource_pack_sha1: Option<String>,
114    pub max_world_size: u32,
115    //generator_settings:todo!(),
116    //text_filtering_config:todo!(),
117}
118
119fn read(filepath: &Path) -> std::io::Result<Properties> {
120    let file = File::open(filepath)?;
121    let mut reader = BufReader::new(file);
122    read_properties::read_properties(&mut reader)
123        .map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
124}
125impl FromStr for Gamemode {
126    type Err = ();
127
128    fn from_str(input: &str) -> Result<Gamemode, Self::Err> {
129        match input.to_lowercase().as_str() {
130            "creative" => Ok(Gamemode::Creative),
131            "survival" => Ok(Gamemode::Survival),
132            "spectator" => Ok(Gamemode::Spectator),
133            "adventure" => Ok(Gamemode::Adventure),
134            _ => Err(()),
135        }
136    }
137}
138impl FromStr for WorldPreset {
139    type Err = ();
140
141    fn from_str(input: &str) -> Result<Self, Self::Err> {
142        match input.to_lowercase().as_str() {
143            "normal" => Ok(WorldPreset::Normal),
144            "flat" => Ok(WorldPreset::Flat),
145            "largebiomes" => Ok(WorldPreset::LargeBiomes),
146            "amplified" => Ok(WorldPreset::Amplified),
147            "singlebiomesurface" => Ok(WorldPreset::SingleBiomeSurface),
148            _ => Err(()),
149        }
150    }
151}
152impl FromStr for Difficulty {
153    type Err = ();
154
155    fn from_str(input: &str) -> Result<Difficulty, Self::Err> {
156        match input.to_lowercase().as_str() {
157            "easy" => Ok(Difficulty::Easy),
158            "normal" => Ok(Difficulty::Normal),
159            "hard" => Ok(Difficulty::Hard),
160            _ => Err(()), // Gère les valeurs inconnues
161        }
162    }
163}
164impl fmt::Display for Difficulty {
165    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166        let difficulty = match self {
167            Difficulty::Easy => "easy",
168            Difficulty::Normal => "normal",
169            Difficulty::Hard => "hard",
170        };
171        write!(f, "{}", difficulty)
172    }
173}
174
175impl Settings {
176    pub fn new() -> Self {
177        let config_file = read(Path::new(crate::consts::file_paths::PROPERTIES))
178            .expect("Error reading {server.properties} file");
179        Self {
180            enable_jmx_monitoring: Self::get_bool(&config_file, "enable-jmx-monitoring", false),
181            rcon_port: Self::get_u16(&config_file, "rcon.port", 25575),
182            level_seed: Self::get_optional_i64(&config_file, "level-seed"),
183            gamemode: Self::get_enum::<Gamemode>(&config_file, "gamemode", Gamemode::Survival),
184            enable_command_block: Self::get_bool(&config_file, "enable-command-block", false),
185            max_players: Self::get_u32(&config_file, "max-players", 20),
186            server_ip: Self::get_optional_ip(&config_file, "server-ip"),
187            server_port: Self::get_u16(&config_file, "server-port", 25565),
188            allow_flight: Self::get_bool(&config_file, "allow-flight", false),
189            allow_nether: Self::get_bool(&config_file, "allow-nether", true),
190            broadcast_console_to_ops: Self::get_bool(
191                &config_file,
192                "broadcast-console-to-ops",
193                true,
194            ),
195            difficulty: config_file
196                .get_property("difficulty")
197                .unwrap_or("easy")
198                .parse::<Difficulty>()
199                .unwrap_or(Difficulty::Easy),
200            broadcast_rcon_to_ops: Self::get_bool(&config_file, "broadcast-rcon-to-ops", true),
201            enable_query: Self::get_bool(&config_file, "enable-query", true),
202            enable_rcon: Self::get_bool(&config_file, "enable-rcon", false),
203            enable_status: Self::get_bool(&config_file, "enable-status", true),
204            enforce_secure_profile: Self::get_bool(&config_file, "enforce-secure-profil", true),
205            enforce_whitelist: Self::get_bool(&config_file, "enforce-whitelist", false),
206            entity_broadcast_range_percentage: Self::get_u8(
207                &config_file,
208                "entity-broadcast-range-percentage",
209                100,
210            ),
211            force_gamemode: Self::get_bool(&config_file, "force-gamemode", false),
212            function_permission_level: Self::get_u8(&config_file, "function-permission-level", 2),
213            generate_structures: Self::get_bool(&config_file, "generate-strucutre", true),
214            hardcore: Self::get_bool(&config_file, "hardcore", false),
215            hide_online_players: Self::get_bool(&config_file, "hide-online-players", false),
216            initial_disabled_packs: Self::get_optional_string(
217                &config_file,
218                "initial-disabled-packs",
219            ),
220            initial_enabled_packs: Self::get_optional_string(&config_file, "initial-enabled-packs")
221                .unwrap_or_else(|| "vanilla".to_string()),
222            level_name: Self::get_optional_string(&config_file, "level-name"),
223            level_type: config_file
224                .get_property("level-type")
225                .unwrap_or("normal")
226                .parse::<WorldPreset>()
227                .unwrap_or(WorldPreset::Normal),
228            log_ips: Self::get_bool(&config_file, "log-ips", true), // this one is obsured
229            max_chained_neighbor_updates: Self::get_optional_i32(
230                &config_file,
231                "max-chained-neighbor-updates",
232            ),
233            max_tick_time: Self::get_i64(&config_file, "max-tick-time", 60000),
234            max_world_size: Self::get_u32(&config_file, "max-world-size", 29999984),
235            motd: Self::get_optional_string(&config_file, "motd"),
236            network_compression_threshold: Self::get_i32(
237                &config_file,
238                "network-compression-threshold",
239                256,
240            ),
241            online_mode: Self::get_bool(&config_file, "online-mode", true),
242            op_permission_level: Self::get_u8(&config_file, "op-permission-level", 4),
243            player_idle_timeout: Self::get_i32(&config_file, "player-idle-timeout", 0),
244            prevent_proxy_connections: Self::get_bool(
245                &config_file,
246                "prevent-proxy-connections",
247                false,
248            ),
249            pvp: Self::get_bool(&config_file, "pvp", true),
250            query_port: Self::get_u16(&config_file, "query-port", 25565),
251            rate_limit: Self::get_u32(&config_file, "rate-limit", 0),
252            rcon_password: Self::get_optional_string(&config_file, "rcon.password"),
253            require_resource_pack: Self::get_bool(&config_file, "require-ressource-pack", false),
254            resource_pack: Self::get_optional_string(&config_file, "resource-pack"),
255            resource_pack_prompt: Self::get_optional_string(&config_file, "resource-pack-prompt"),
256            resource_pack_sha1: Self::get_optional_string(&config_file, "ressource-pack-sha1"),
257            simulation_distance: Self::get_u8(&config_file, "simulation-distance", 10),
258            spawn_animals: Self::get_bool(&config_file, "spawn-animals", true),
259            spawn_monsters: Self::get_bool(&config_file, "spawn-monster", true),
260            spawn_npcs: Self::get_bool(&config_file, "spawn-npcs", true),
261            spawn_protection: Self::get_u16(&config_file, "spawn-protection", 16),
262            sync_chunk_writes: Self::get_bool(&config_file, "sync-chunk-writes", false),
263            use_native_transport: Self::get_bool(&config_file, "use-native-transport", true),
264            view_distance: Self::get_u8(&config_file, "view-distance", 10),
265            white_list: Self::get_bool(&config_file, "white-list", false),
266        }
267    }
268    fn get_bool(config: &Properties, key: &str, default: bool) -> bool {
269        config
270            .get_property(key)
271            .unwrap_or(default.to_string().as_str())
272            .parse()
273            .unwrap_or(default)
274    }
275    fn get_u16(config: &Properties, key: &str, default: u16) -> u16 {
276        config
277            .get_property(key)
278            .unwrap_or(default.to_string().as_str())
279            .parse()
280            .unwrap_or(default)
281    }
282    fn get_u32(config: &Properties, key: &str, default: u32) -> u32 {
283        config
284            .get_property(key)
285            .unwrap_or(default.to_string().as_str())
286            .parse()
287            .unwrap_or(default)
288    }
289    fn get_u8(config: &Properties, key: &str, default: u8) -> u8 {
290        config
291            .get_property(key)
292            .unwrap_or(default.to_string().as_str())
293            .parse()
294            .unwrap_or(default)
295    }
296    fn get_i32(config: &Properties, key: &str, default: i32) -> i32 {
297        config
298            .get_property(key)
299            .unwrap_or(default.to_string().as_str())
300            .parse()
301            .unwrap_or(default)
302    }
303    fn get_i64(config: &Properties, key: &str, default: i64) -> i64 {
304        config
305            .get_property(key)
306            .unwrap_or(default.to_string().as_str())
307            .parse()
308            .unwrap_or(default)
309    }
310    fn get_enum<T: std::str::FromStr>(config: &Properties, key: &str, default: T) -> T {
311        config
312            .get_property(key)
313            .unwrap_or("")
314            .parse()
315            .unwrap_or(default)
316    }
317    fn get_optional_string(config: &Properties, key: &str) -> Option<String> {
318        config
319            .get_property(key)
320            .ok()
321            .filter(|s| !s.is_empty())
322            .map(String::from)
323    }
324
325    fn get_optional_i64(config: &Properties, key: &str) -> Option<i64> {
326        config.get_property(key).ok().and_then(|s| s.parse().ok())
327    }
328    fn get_optional_i32(config: &Properties, key: &str) -> Option<i32> {
329        config.get_property(key).ok().and_then(|s| s.parse().ok())
330    }
331
332    fn get_optional_ip(config: &Properties, key: &str) -> Option<Ipv4Addr> {
333        config.get_property(key).ok().and_then(|s| s.parse().ok())
334    }
335}