Skip to main content

rpfm_extensions/search/
portrait_settings.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11use getset::{Getters, MutGetters, Setters};
12use serde_derive::{Deserialize, Serialize};
13
14use rpfm_lib::files::portrait_settings::PortraitSettings;
15
16use super::{find_in_string, MatchingMode, Replaceable, SearchSource, Searchable, replace_match_string};
17
18//-------------------------------------------------------------------------------//
19//                              Enums & Structs
20//-------------------------------------------------------------------------------//
21
22/// This struct represents all the matches of the global search within an PortraitSettings File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct PortraitSettingsMatches {
26
27    /// The path of the file.
28    path: String,
29
30    /// The search source that produced these matches.
31    #[serde(default)]
32    source: SearchSource,
33
34    /// The container name (pack file name) this file belongs to.
35    #[serde(default)]
36    container_name: String,
37
38    /// The list of matches within the file.
39    matches: Vec<PortraitSettingsMatch>,
40}
41
42/// This struct represents a match within an PortraitSettings File.
43#[derive(Debug, Default, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct PortraitSettingsMatch {
46
47    /// The index of the entry in question in the PortraitSettings file. Not sure if the ids are unique, so we use the index.
48    entry: usize,
49
50    /// If the match corresponds to the id.
51    id: bool,
52
53    /// If the match corresponds to a camera settings head (skeleton node) value.
54    camera_settings_head: bool,
55
56    /// If the match corresponds to a camera settings body (skeleton node) value.
57    camera_settings_body: bool,
58
59    /// If the match corresponds to a variant value. We have their index and a bool for each value.
60    variant: Option<(usize, bool, bool, bool, bool, bool)>,
61
62    /// Byte where the match starts.
63    start: usize,
64
65    /// Byte where the match ends.
66    end: usize,
67
68    /// Matched data.
69    text: String,
70}
71
72//-------------------------------------------------------------------------------//
73//                             Implementations
74//-------------------------------------------------------------------------------//
75
76impl Searchable for PortraitSettings {
77    type SearchMatches = PortraitSettingsMatches;
78
79    fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> PortraitSettingsMatches {
80        let mut matches = PortraitSettingsMatches::new(file_path);
81
82        match matching_mode {
83            MatchingMode::Regex(regex) => {
84                for (index, data) in self.entries().iter().enumerate() {
85                    for entry_match in regex.find_iter(data.id()) {
86                        matches.matches.push(
87                            PortraitSettingsMatch::new(
88                                index,
89                                true,
90                                false,
91                                false,
92                                None,
93                                entry_match.start(),
94                                entry_match.end(),
95                                data.id().to_owned()
96                            )
97                        );
98                    }
99
100                    for entry_match in regex.find_iter(data.camera_settings_head().skeleton_node()) {
101                        matches.matches.push(
102                            PortraitSettingsMatch::new(
103                                index,
104                                false,
105                                true,
106                                false,
107                                None,
108                                entry_match.start(),
109                                entry_match.end(),
110                                data.camera_settings_head().skeleton_node().to_owned()
111                            )
112                        );
113                    }
114
115                    if let Some(camera_body) = data.camera_settings_body() {
116                        for entry_match in regex.find_iter(camera_body.skeleton_node()) {
117                            matches.matches.push(
118                                PortraitSettingsMatch::new(
119                                    index,
120                                    false,
121                                    false,
122                                    true,
123                                    None,
124                                    entry_match.start(),
125                                    entry_match.end(),
126                                    camera_body.skeleton_node().to_owned()
127                                )
128                            );
129                        }
130                    }
131
132                    for (vindex, variant) in data.variants().iter().enumerate() {
133                        for entry_match in regex.find_iter(variant.filename()) {
134                            matches.matches.push(
135                                PortraitSettingsMatch::new(
136                                    index,
137                                    false,
138                                    false,
139                                    false,
140                                    Some((vindex, true, false, false, false, false)),
141                                    entry_match.start(),
142                                    entry_match.end(),
143                                    variant.filename().to_owned()
144                                )
145                            );
146                        }
147
148                        for entry_match in regex.find_iter(variant.file_diffuse()) {
149                            matches.matches.push(
150                                PortraitSettingsMatch::new(
151                                    index,
152                                    false,
153                                    false,
154                                    false,
155                                    Some((vindex, false, true, false, false, false)),
156                                    entry_match.start(),
157                                    entry_match.end(),
158                                    variant.file_diffuse().to_owned()
159                                )
160                            );
161                        }
162
163                        for entry_match in regex.find_iter(variant.file_mask_1()) {
164                            matches.matches.push(
165                                PortraitSettingsMatch::new(
166                                    index,
167                                    false,
168                                    false,
169                                    false,
170                                    Some((vindex, false, false, true, false, false)),
171                                    entry_match.start(),
172                                    entry_match.end(),
173                                    variant.file_mask_1().to_owned()
174                                )
175                            );
176                        }
177
178                        for entry_match in regex.find_iter(variant.file_mask_2()) {
179                            matches.matches.push(
180                                PortraitSettingsMatch::new(
181                                    index,
182                                    false,
183                                    false,
184                                    false,
185                                    Some((vindex, false, false, false, true, false)),
186                                    entry_match.start(),
187                                    entry_match.end(),
188                                    variant.file_mask_2().to_owned()
189                                )
190                            );
191                        }
192
193                        for entry_match in regex.find_iter(variant.file_mask_3()) {
194                            matches.matches.push(
195                                PortraitSettingsMatch::new(
196                                    index,
197                                    false,
198                                    false,
199                                    false,
200                                    Some((vindex, false, false, false, false, true)),
201                                    entry_match.start(),
202                                    entry_match.end(),
203                                    variant.file_mask_3().to_owned()
204                                )
205                            );
206                        }
207                    }
208                }
209            }
210
211            MatchingMode::Pattern(regex) => {
212                let pattern = if case_sensitive || regex.is_some() {
213                    pattern.to_owned()
214                } else {
215                    pattern.to_lowercase()
216                };
217
218                for (index, data) in self.entries().iter().enumerate() {
219
220                    for (start, end, _) in &find_in_string(data.id(), &pattern, case_sensitive, regex) {
221                        matches.matches.push(
222                            PortraitSettingsMatch::new(
223                                index,
224                                true,
225                                false,
226                                false,
227                                None,
228                                *start,
229                                *end,
230                                data.id().to_owned()
231                            )
232                        );
233
234                    }
235
236                    for (start, end, _) in &find_in_string(data.camera_settings_head().skeleton_node(), &pattern, case_sensitive, regex) {
237                        matches.matches.push(
238                            PortraitSettingsMatch::new(
239                                index,
240                                false,
241                                true,
242                                false,
243                                None,
244                                *start,
245                                *end,
246                                data.camera_settings_head().skeleton_node().to_owned()
247                            )
248                        );
249                    }
250
251                    if let Some(camera_body) = data.camera_settings_body() {
252                        for (start, end, _) in &find_in_string(camera_body.skeleton_node(), &pattern, case_sensitive, regex) {
253                            matches.matches.push(
254                                PortraitSettingsMatch::new(
255                                    index,
256                                    false,
257                                    false,
258                                    true,
259                                    None,
260                                    *start,
261                                    *end,
262                                    camera_body.skeleton_node().to_owned()
263                                )
264                            );
265                        }
266                    }
267
268                    for (vindex, variant) in data.variants().iter().enumerate() {
269                        for (start, end, _) in &find_in_string(variant.filename(), &pattern, case_sensitive, regex) {
270                            matches.matches.push(
271                                PortraitSettingsMatch::new(
272                                    index,
273                                    false,
274                                    false,
275                                    false,
276                                    Some((vindex, true, false, false, false, false)),
277                                    *start,
278                                    *end,
279                                    variant.filename().to_owned()
280                                )
281                            );
282                        }
283
284                        for (start, end, _) in &find_in_string(variant.file_diffuse(), &pattern, case_sensitive, regex) {
285                            matches.matches.push(
286                                PortraitSettingsMatch::new(
287                                    index,
288                                    false,
289                                    false,
290                                    false,
291                                    Some((vindex, false, true, false, false, false)),
292                                    *start,
293                                    *end,
294                                    variant.file_diffuse().to_owned()
295                                )
296                            );
297                        }
298
299                        for (start, end, _) in &find_in_string(variant.file_mask_1(), &pattern, case_sensitive, regex) {
300                            matches.matches.push(
301                                PortraitSettingsMatch::new(
302                                    index,
303                                    false,
304                                    false,
305                                    false,
306                                    Some((vindex, false, false, true, false, false)),
307                                    *start,
308                                    *end,
309                                    variant.file_mask_1().to_owned()
310                                )
311                            );
312                        }
313
314                        for (start, end, _) in &find_in_string(variant.file_mask_2(), &pattern, case_sensitive, regex) {
315                            matches.matches.push(
316                                PortraitSettingsMatch::new(
317                                    index,
318                                    false,
319                                    false,
320                                    false,
321                                    Some((vindex, false, false, false, true, false)),
322                                    *start,
323                                    *end,
324                                    variant.file_mask_2().to_owned()
325                                )
326                            );
327                        }
328
329                        for (start, end, _) in &find_in_string(variant.file_mask_3(), &pattern, case_sensitive, regex) {
330                            matches.matches.push(
331                                PortraitSettingsMatch::new(
332                                    index,
333                                    false,
334                                    false,
335                                    false,
336                                    Some((vindex, false, false, false, false, true)),
337                                    *start,
338                                    *end,
339                                    variant.file_mask_3().to_owned()
340                                )
341                            );
342                        }
343                    }
344                }
345            }
346        }
347
348        matches
349    }
350}
351
352impl Replaceable for PortraitSettings {
353
354    fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &PortraitSettingsMatches) -> bool {
355        let mut edited = false;
356
357        // NOTE: Due to changes in index positions, we need to do this in reverse.
358        // Otherwise we may cause one edit to generate invalid indexes for the next matches.
359        for search_match in search_matches.matches().iter().rev() {
360            edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
361        }
362
363        edited
364    }
365}
366
367impl PortraitSettingsMatches {
368
369    /// This function creates a new `PortraitSettingsMatches` for the provided path.
370    pub fn new(path: &str) -> Self {
371        Self {
372            path: path.to_owned(),
373            matches: vec![],
374            source: SearchSource::default(),
375            container_name: String::new(),
376        }
377    }
378}
379
380impl PortraitSettingsMatch {
381
382    /// This function creates a new `PortraitSettingsMatch` with the provided data.
383    pub fn new(entry: usize, id: bool, camera_settings_head: bool, camera_settings_body: bool, variant: Option<(usize, bool, bool, bool, bool, bool)>, start: usize, end: usize, data: String) -> Self {
384        Self {
385            entry,
386            id,
387            camera_settings_head,
388            camera_settings_body,
389            variant,
390            start,
391            end,
392            text: data
393        }
394    }
395
396    /// This function replaces all the matches in the provided data.
397    fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut PortraitSettings) -> bool {
398        let mut edited = false;
399
400        if let Some(entry) = data.entries_mut().get_mut(self.entry) {
401
402            // Get all the previous data and references of data to manipulate here, so we don't duplicate a lot of code per-field in the match mode part.
403            let (previous_data, current_data) = {
404                if self.id {
405                    (entry.id().to_owned(), entry.id_mut())
406                } else if self.camera_settings_head {
407                    (entry.camera_settings_head().skeleton_node().to_owned(), entry.camera_settings_head_mut().skeleton_node_mut())
408                } else if self.camera_settings_body {
409                    match entry.camera_settings_body_mut() {
410                        Some(body) => (body.skeleton_node().to_owned(), body.skeleton_node_mut()),
411                        None => return false,
412                    }
413                } else if let Some((vindex, filename, file_diffuse, file_mask_1, file_mask_2, file_mask_3)) = self.variant {
414                    match entry.variants_mut().get_mut(vindex) {
415                        Some(variant) => {
416                            if filename {
417                                (variant.filename().to_owned(), variant.filename_mut())
418                            } else if file_diffuse {
419                                (variant.file_diffuse().to_owned(), variant.file_diffuse_mut())
420                            } else if file_mask_1 {
421                                (variant.file_mask_1().to_owned(), variant.file_mask_1_mut())
422                            } else if file_mask_2 {
423                                (variant.file_mask_2().to_owned(), variant.file_mask_2_mut())
424                            } else if file_mask_3 {
425                                (variant.file_mask_3().to_owned(), variant.file_mask_3_mut())
426                            } else {
427                                return false;
428                            }
429                        }
430                        None => return false,
431                    }
432                }
433
434                // This is an error.
435                else {
436                    return false
437                }
438            };
439
440            edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data);
441        }
442
443        edited
444    }
445}