Skip to main content

rpfm_extensions/search/
rigid_model.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::rigidmodel::RigidModel;
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 RigidModel File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct RigidModelMatches {
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<RigidModelMatch>,
40}
41
42/// This struct represents a match within an RigidModel File.
43#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct RigidModelMatch {
46
47    /// If the match is in the skeleton id of the rigid.
48    skeleton_id: bool,
49
50    /// If the match is inside a mesh. The values are
51    /// - Lod index.
52    /// - Mesh index.
53    mesh_value: Option<(i32, i32)>,
54
55    /// If the match is in the name of a mesh.
56    mesh_name: bool,
57
58    /// If the match is in the name of the material.
59    mesh_mat_name: bool,
60
61    /// If the match is in the texture directory of a mesh.
62    mesh_textute_directory: bool,
63
64    /// If the match is in the filters of a mesh.
65    mesh_filters: bool,
66
67    /// If the match is in the name of one of the attachment points of a mesh.
68    mesh_att_point_name: Option<i32>,
69
70    /// If the match is in the name of one of the texture paths of a mesh.
71    mesh_texture_path: Option<i32>,
72
73    /// Byte where the match starts.
74    start: usize,
75
76    /// Byte where the match ends.
77    end: usize,
78
79    /// Matched data.
80    text: String,
81}
82
83//-------------------------------------------------------------------------------//
84//                             Implementations
85//-------------------------------------------------------------------------------//
86
87impl Searchable for RigidModel {
88    type SearchMatches = RigidModelMatches;
89
90    fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> RigidModelMatches {
91        let mut matches = RigidModelMatches::new(file_path);
92
93        match matching_mode {
94            MatchingMode::Regex(regex) => {
95
96                for entry_match in regex.find_iter(self.skeleton_id()) {
97                    matches.matches.push(
98                        RigidModelMatch::new(
99                            true,
100                            None,
101                            false,
102                            false,
103                            false,
104                            false,
105                            None,
106                            None,
107                            entry_match.start(),
108                            entry_match.end(),
109                            self.skeleton_id().to_owned()
110                        )
111                    );
112                }
113
114                for (l_index, lod) in self.lods().iter().enumerate() {
115                    for (m_index, mesh) in lod.mesh_blocks().iter().enumerate() {
116                        for entry_match in regex.find_iter(mesh.mesh().name()) {
117                            matches.matches.push(
118                                RigidModelMatch::new(
119                                    false,
120                                    Some((l_index as i32, m_index as i32)),
121                                    true,
122                                    false,
123                                    false,
124                                    false,
125                                    None,
126                                    None,
127                                    entry_match.start(),
128                                    entry_match.end(),
129                                    mesh.mesh().name().to_owned()
130                                )
131                            );
132                        }
133
134                        for entry_match in regex.find_iter(mesh.material().name()) {
135                            matches.matches.push(
136                                RigidModelMatch::new(
137                                    false,
138                                    Some((l_index as i32, m_index as i32)),
139                                    false,
140                                    true,
141                                    false,
142                                    false,
143                                    None,
144                                    None,
145                                    entry_match.start(),
146                                    entry_match.end(),
147                                    mesh.material().name().to_owned()
148                                )
149                            );
150                        }
151
152                        for entry_match in regex.find_iter(mesh.material().texture_directory()) {
153                            matches.matches.push(
154                                RigidModelMatch::new(
155                                    false,
156                                    Some((l_index as i32, m_index as i32)),
157                                    false,
158                                    false,
159                                    true,
160                                    false,
161                                    None,
162                                    None,
163                                    entry_match.start(),
164                                    entry_match.end(),
165                                    mesh.material().texture_directory().to_owned()
166                                )
167                            );
168                        }
169
170                        for entry_match in regex.find_iter(mesh.material().filters()) {
171                            matches.matches.push(
172                                RigidModelMatch::new(
173                                    false,
174                                    Some((l_index as i32, m_index as i32)),
175                                    false,
176                                    false,
177                                    false,
178                                    true,
179                                    None,
180                                    None,
181                                    entry_match.start(),
182                                    entry_match.end(),
183                                    mesh.material().filters().to_owned()
184                                )
185                            );
186                        }
187
188                        for (a_index, attachment) in mesh.material().attachment_points().iter().enumerate() {
189                            for entry_match in regex.find_iter(attachment.name()) {
190                                matches.matches.push(
191                                    RigidModelMatch::new(
192                                        false,
193                                        Some((l_index as i32, m_index as i32)),
194                                        false,
195                                        false,
196                                        false,
197                                        false,
198                                        Some(a_index as i32),
199                                        None,
200                                        entry_match.start(),
201                                        entry_match.end(),
202                                        attachment.name().to_owned()
203                                    )
204                                );
205                            }
206                        }
207
208                        for (t_index, texture) in mesh.material().textures().iter().enumerate() {
209                            for entry_match in regex.find_iter(texture.path()) {
210                                matches.matches.push(
211                                    RigidModelMatch::new(
212                                        false,
213                                        Some((l_index as i32, m_index as i32)),
214                                        false,
215                                        false,
216                                        false,
217                                        false,
218                                        None,
219                                        Some(t_index as i32),
220                                        entry_match.start(),
221                                        entry_match.end(),
222                                        texture.path().to_owned()
223                                    )
224                                );
225                            }
226                        }
227                    }
228                }
229            }
230
231            MatchingMode::Pattern(regex) => {
232                let pattern = if case_sensitive || regex.is_some() {
233                    pattern.to_owned()
234                } else {
235                    pattern.to_lowercase()
236                };
237
238                for (start, end, _) in &find_in_string(self.skeleton_id(), &pattern, case_sensitive, regex) {
239                    matches.matches.push(
240                        RigidModelMatch::new(
241                            true,
242                            None,
243                            false,
244                            false,
245                            false,
246                            false,
247                            None,
248                            None,
249                            *start,
250                            *end,
251                            self.skeleton_id().to_owned()
252                        )
253                    );
254                }
255
256                for (l_index, lod) in self.lods().iter().enumerate() {
257                    for (m_index, mesh) in lod.mesh_blocks().iter().enumerate() {
258                        for (start, end, _) in &find_in_string(mesh.mesh().name(), &pattern, case_sensitive, regex) {
259                            matches.matches.push(
260                                RigidModelMatch::new(
261                                    false,
262                                    Some((l_index as i32, m_index as i32)),
263                                    true,
264                                    false,
265                                    false,
266                                    false,
267                                    None,
268                                    None,
269                                    *start,
270                                    *end,
271                                    mesh.mesh().name().to_owned()
272                                )
273                            );
274                        }
275
276                        for (start, end, _) in &find_in_string(mesh.material().name(), &pattern, case_sensitive, regex) {
277                            matches.matches.push(
278                                RigidModelMatch::new(
279                                    false,
280                                    Some((l_index as i32, m_index as i32)),
281                                    false,
282                                    true,
283                                    false,
284                                    false,
285                                    None,
286                                    None,
287                                    *start,
288                                    *end,
289                                    mesh.material().name().to_owned()
290                                )
291                            );
292                        }
293
294                        for (start, end, _) in &find_in_string(mesh.material().texture_directory(), &pattern, case_sensitive, regex) {
295                            matches.matches.push(
296                                RigidModelMatch::new(
297                                    false,
298                                    Some((l_index as i32, m_index as i32)),
299                                    false,
300                                    false,
301                                    true,
302                                    false,
303                                    None,
304                                    None,
305                                    *start,
306                                    *end,
307                                    mesh.material().texture_directory().to_owned()
308                                )
309                            );
310                        }
311
312                        for (start, end, _) in &find_in_string(mesh.material().filters(), &pattern, case_sensitive, regex) {
313                            matches.matches.push(
314                                RigidModelMatch::new(
315                                    false,
316                                    Some((l_index as i32, m_index as i32)),
317                                    false,
318                                    false,
319                                    false,
320                                    true,
321                                    None,
322                                    None,
323                                    *start,
324                                    *end,
325                                    mesh.material().filters().to_owned()
326                                )
327                            );
328                        }
329
330                        for (a_index, attachment) in mesh.material().attachment_points().iter().enumerate() {
331                            for (start, end, _) in &find_in_string(attachment.name(), &pattern, case_sensitive, regex) {
332                                matches.matches.push(
333                                    RigidModelMatch::new(
334                                        false,
335                                        Some((l_index as i32, m_index as i32)),
336                                        false,
337                                        false,
338                                        false,
339                                        false,
340                                        Some(a_index as i32),
341                                        None,
342                                        *start,
343                                        *end,
344                                        attachment.name().to_owned()
345                                    )
346                                );
347                            }
348                        }
349
350                        for (t_index, texture) in mesh.material().textures().iter().enumerate() {
351                            for (start, end, _) in &find_in_string(texture.path(), &pattern, case_sensitive, regex) {
352                                matches.matches.push(
353                                    RigidModelMatch::new(
354                                        false,
355                                        Some((l_index as i32, m_index as i32)),
356                                        false,
357                                        false,
358                                        false,
359                                        false,
360                                        None,
361                                        Some(t_index as i32),
362                                        *start,
363                                        *end,
364                                        texture.path().to_owned()
365                                    )
366                                );
367                            }
368                        }
369                    }
370                }
371            }
372        }
373
374        matches
375    }
376}
377
378impl Replaceable for RigidModel {
379
380    fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &RigidModelMatches) -> bool {
381        let mut edited = false;
382
383        // NOTE: Due to changes in index positions, we need to do this in reverse.
384        // Otherwise we may cause one edit to generate invalid indexes for the next matches.
385        for search_match in search_matches.matches().iter().rev() {
386            edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
387        }
388
389        edited
390    }
391}
392
393impl RigidModelMatches {
394
395    /// This function creates a new `RigidModelMatches` for the provided path.
396    pub fn new(path: &str) -> Self {
397        Self {
398            path: path.to_owned(),
399            matches: vec![],
400            source: SearchSource::default(),
401            container_name: String::new(),
402        }
403    }
404}
405
406impl RigidModelMatch {
407
408    /// This function creates a new `RigidModelMatch` with the provided data.
409    pub fn new(
410        skeleton_id: bool,
411        mesh_value: Option<(i32, i32)>,
412        mesh_name: bool,
413        mesh_mat_name: bool,
414        mesh_textute_directory: bool,
415        mesh_filters: bool,
416        mesh_att_point_name: Option<i32>,
417        mesh_texture_path: Option<i32>,
418        start: usize,
419        end: usize,
420        data: String
421    ) -> Self {
422        Self {
423            skeleton_id,
424            mesh_value,
425            mesh_name,
426            mesh_mat_name,
427            mesh_textute_directory,
428            mesh_filters,
429            mesh_att_point_name,
430            mesh_texture_path,
431            start,
432            end,
433            text: data,
434        }
435    }
436
437    /// This function replaces all the matches in the provided data.
438    fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut RigidModel) -> bool {
439
440        // 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.
441        let (previous_data, current_data) = {
442            if self.skeleton_id {
443                (data.skeleton_id().to_owned(), data.skeleton_id_mut())
444            } else if let Some((l_index, m_index)) = self.mesh_value {
445                if let Some(lod) = data.lods_mut().get_mut(l_index as usize) {
446                    if let Some(mesh) = lod.mesh_blocks_mut().get_mut(m_index as usize) {
447                        if self.mesh_name {
448                            (mesh.mesh().name().to_owned(), mesh.mesh_mut().name_mut())
449                        } else if self.mesh_mat_name {
450                            (mesh.material().name().to_owned(), mesh.material_mut().name_mut())
451                        } else if self.mesh_textute_directory {
452                            (mesh.material().texture_directory().to_owned(), mesh.material_mut().texture_directory_mut())
453                        } else if self.mesh_filters {
454                            (mesh.material().filters().to_owned(), mesh.material_mut().filters_mut())
455                        } else if let Some(a_index) = self.mesh_att_point_name {
456                            if let Some(att) = mesh.material_mut().attachment_points_mut().get_mut(a_index as usize) {
457                                (att.name().to_owned(), att.name_mut())
458                            } else {
459                                return false
460                            }
461                        } else if let Some(t_index) = self.mesh_texture_path {
462                            if let Some(tex) = mesh.material_mut().textures_mut().get_mut(t_index as usize) {
463                                (tex.path().to_owned(), tex.path_mut())
464                            } else {
465                                return false
466                            }
467                        } else {
468                            return false
469                        }
470                    } else {
471                        return false
472                    }
473                } else {
474                    return false
475                }
476            } else {
477                return false
478            }
479        };
480
481        replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data)
482    }
483}