1use glacier_base::encryption::xtea::{Xtea, XteaError};
2use indexmap::IndexMap;
3use itertools::Itertools;
4use std::collections::HashMap;
5use std::io::Write;
6use std::ops::{Index, IndexMut};
7use thiserror::Error;
8
9#[derive(Error, Debug)]
10pub enum IniFileError {
11 #[error("Option ({}) not found", _0)]
12 OptionNotFound(String),
13
14 #[error("Can't find section ({})", _0)]
15 SectionNotFound(String),
16
17 #[error("An error occurred when parsing: {}", _0)]
18 ParsingError(String),
19
20 #[error("An io error occurred: {}", _0)]
21 IoError(#[from] std::io::Error),
22
23 #[error("An io error occurred: {}", _0)]
24 DecryptionError(#[from] XteaError),
25
26 #[error("The given input was incorrect: {}", _0)]
27 InvalidInput(String),
28
29 #[error("The requested include addition already exists: {}", _0)]
30 IncludeAlreadyExists(String),
31}
32
33impl IniFileSection {
34 pub fn new(name: &str) -> Self {
35 Self {
36 name: name.to_string(),
37 options: IndexMap::new(),
38 }
39 }
40
41 pub fn name(&self) -> String {
42 self.name.to_owned()
43 }
44
45 pub fn options(&self) -> &IndexMap<String, String> {
46 &self.options
47 }
48
49 pub fn has_option(&self, option_name: &str) -> bool {
50 self.options.contains_key(option_name)
51 }
52
53 pub fn option(&self, option_name: &str) -> Option<String> {
54 self.options.get(option_name).cloned()
55 }
56
57 pub fn with_option(&mut self, option_name: &str, value: &str) -> &mut Self {
58 self.insert(option_name, value);
59 self
60 }
61
62 pub fn insert(&mut self, option_name: &str, value: &str) {
63 if let Some(key) = self.options.get_mut(option_name) {
64 *key = value.to_string();
65 } else {
66 self.options
67 .insert(option_name.to_string(), value.to_string());
68 }
69 }
70
71 pub fn write_section<W: std::fmt::Write>(&self, writer: &mut W) {
72 writeln!(writer, "[{}]", self.name).unwrap();
73 for (key, value) in &self.options {
74 writeln!(writer, "{key}={value}").unwrap();
75 }
76 writeln!(writer).unwrap();
77 }
78}
79
80#[derive(Default, Debug, Eq, PartialEq)]
81pub struct IniFileSection {
82 pub(crate) name: String,
83 pub(crate) options: IndexMap<String, String>,
84}
85
86#[derive(Debug, Eq, PartialEq)]
102pub struct IniFile {
103 pub(crate) name: String,
104 pub(crate) description: Option<String>,
105 pub(crate) includes: Vec<IniFile>,
106 pub(crate) sections: HashMap<String, IniFileSection>,
107 pub(crate) console_cmds: Vec<String>,
108}
109
110impl Index<&str> for IniFileSection {
111 type Output = str;
112
113 fn index(&self, option_name: &str) -> &str {
114 self.options.get(option_name).expect("Option not found")
115 }
116}
117
118impl IndexMut<&str> for IniFileSection {
119 fn index_mut(&mut self, option_name: &str) -> &mut str {
120 self.options.entry(option_name.to_string()).or_default()
121 }
122}
123
124impl Default for IniFile {
125 fn default() -> Self {
126 Self {
127 name: "thumbs.dat".to_string(),
128 description: Some(String::from("System config file for the engine")),
129 includes: vec![],
130 sections: Default::default(),
131 console_cmds: vec![],
132 }
133 }
134}
135
136impl IniFile {
137 pub fn new(name: &str) -> Self {
138 Self {
139 name: name.to_string(),
140 description: None,
141 includes: vec![],
142 sections: Default::default(),
143 console_cmds: vec![],
144 }
145 }
146 pub fn name(&self) -> String {
147 self.name.to_string()
148 }
149 pub fn sections(&self) -> &HashMap<String, IniFileSection> {
150 &self.sections
151 }
152
153 pub fn includes(&self) -> &Vec<IniFile> {
154 &self.includes
155 }
156
157 pub fn get_or_add_include(&mut self, include_name: &str) -> &mut IniFile {
158 if self.find_include_mut(include_name).is_none() {
159 self.add_include(IniFile::new(include_name)).unwrap();
160 }
161 self.find_include_mut(include_name).unwrap()
162 }
163
164 pub fn find_include(&self, include_name: &str) -> Option<&IniFile> {
165 self.includes.iter().find(|incl| incl.name == include_name)
166 }
167
168 pub fn find_include_mut(&mut self, include_name: &str) -> Option<&mut IniFile> {
169 self.includes
170 .iter_mut()
171 .find(|incl| incl.name == include_name)
172 }
173
174 pub fn get_option(
175 &self,
176 section_name: &str,
177 option_name: &str,
178 ) -> Result<String, IniFileError> {
179 match self.sections.get(section_name) {
180 Some(v) => match v.options.get(option_name) {
181 Some(o) => Ok(o.clone()),
182 None => Err(IniFileError::OptionNotFound(option_name.to_string())),
183 },
184 None => Err(IniFileError::SectionNotFound(section_name.to_string())),
185 }
186 }
187
188 pub fn set_description(&mut self, description: &str) {
189 self.description = Some(description.to_string())
190 }
191
192 pub fn with_description(&mut self, description: &str) -> &mut Self {
193 self.description = Some(description.to_string());
194 self
195 }
196
197 pub fn with_command(&mut self, command: &str) -> &mut Self {
198 self.console_cmds.push(command.to_string());
199 self
200 }
201
202 pub fn add_section(&mut self, section: IniFileSection) {
203 self.sections.insert(section.name.to_owned(), section);
204 }
205
206 pub fn with_section(&mut self, name: &str) -> &mut IniFileSection {
207 self.add_section(IniFileSection::new(name));
208 self.sections.get_mut(name).unwrap()
209 }
210
211 pub fn add_new_section(&mut self, section_name: &str, values: Option<Vec<(&str, &str)>>) {
212 match self.sections.get_mut(section_name) {
213 None => {
214 self.sections
215 .insert(section_name.to_string(), IniFileSection::new(section_name));
216 self.add_new_section(section_name, values);
217 }
218 Some(section) => {
219 if let Some(values) = values {
220 for (key, val) in values {
221 section.insert(key, val);
222 }
223 }
224 }
225 }
226 }
227
228 pub fn section(&self, name: &str) -> Option<&IniFileSection> {
229 self.sections.get(name)
230 }
231
232 pub fn section_mut(&mut self, name: &str) -> Option<&mut IniFileSection> {
233 self.sections.get_mut(name)
234 }
235
236 pub fn set_value(
237 &mut self,
238 section_name: &str,
239 option_name: &str,
240 value: &str,
241 ) -> Result<(), IniFileError> {
242 match self.sections.get_mut(section_name) {
243 Some(v) => match v.options.get_mut(option_name) {
244 Some(o) => {
245 *o = value.to_string();
246 Ok(())
247 }
248 None => Err(IniFileError::OptionNotFound(option_name.to_string())),
249 },
250 None => Err(IniFileError::SectionNotFound(section_name.to_string())),
251 }
252 }
253
254 pub fn push_console_command(&mut self, command: String) {
255 self.console_cmds.push(command);
256 }
257
258 pub fn add_include(&mut self, include: IniFile) -> Result<(), IniFileError> {
259 if self.includes.contains(&include) {
260 return Err(IniFileError::IncludeAlreadyExists(include.name));
261 }
262 self.includes.push(include);
263 Ok(())
264 }
265
266 pub fn console_cmds(&self) -> &Vec<String> {
267 &self.console_cmds
268 }
269
270 pub fn write_to_file<W: Write>(&self, writer: &mut W, xtea: &Xtea) -> Result<(), IniFileError> {
271 let mut string = String::new();
272 self.write_ini(&mut string);
273 let data = xtea.encrypt_text_file(string)?;
274 writer
275 .write_all(data.as_slice())
276 .map_err(IniFileError::IoError)
277 }
278
279 pub(crate) fn write_ini<W: std::fmt::Write>(&self, writer: &mut W) {
280 if let Some(description) = &self.description {
281 writeln!(writer, "# {description}").unwrap();
282 writeln!(writer, "\n# -----------------------------------------------------------------------------\n", ).unwrap();
283 }
284
285 for section_name in self
286 .sections
287 .keys()
288 .sorted_by(|a, b| Ord::cmp(&a.to_lowercase(), &b.to_lowercase()))
289 {
290 if let Some(section) = self.sections().get(section_name) {
291 section.write_section(writer);
292 }
293 }
294 for console_cmd in &self.console_cmds {
295 writeln!(writer, "ConsoleCmd {console_cmd}").unwrap();
296 }
297 if !self.includes.is_empty() {
298 writeln!(writer).unwrap();
299 }
300 for include in &self.includes {
301 writeln!(writer, "!include {}", include.name).unwrap();
302 }
303 }
304}
305
306impl Index<&str> for IniFile {
307 type Output = IniFileSection;
308
309 fn index(&self, section_name: &str) -> &IniFileSection {
310 self.sections.get(section_name).expect("Section not found")
311 }
312}
313
314impl IndexMut<&str> for IniFile {
315 fn index_mut(&mut self, section_name: &str) -> &mut IniFileSection {
316 self.sections
317 .entry(section_name.to_string())
318 .or_insert(IniFileSection::new(section_name))
319 }
320}