Skip to main content

rpfm_extensions/search/
unknown.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 regex::bytes::RegexBuilder;
13use serde_derive::{Deserialize, Serialize};
14
15use rpfm_lib::files::unknown::Unknown;
16
17use super::{find_in_bytes, MatchingMode, Replaceable, SearchSource, Searchable, replace_match_bytes};
18
19//-------------------------------------------------------------------------------//
20//                              Enums & Structs
21//-------------------------------------------------------------------------------//
22
23/// This struct represents all the matches of the global search within an Unknown File.
24#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
25#[getset(get = "pub", get_mut = "pub", set = "pub")]
26pub struct UnknownMatches {
27
28    /// The path of the file.
29    path: String,
30
31    /// The search source that produced these matches.
32    #[serde(default)]
33    source: SearchSource,
34
35    /// The container name (pack file name) this file belongs to.
36    #[serde(default)]
37    container_name: String,
38
39    /// The list of matches within the file.
40    matches: Vec<UnknownMatch>,
41}
42
43/// This struct represents a match within an Unknown File.
44#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
45#[getset(get = "pub", get_mut = "pub")]
46pub struct UnknownMatch {
47
48    /// First Byte index of the match.
49    pos: usize,
50
51    /// Length of the matched pattern, in bytes.
52    len: usize,
53}
54
55//-------------------------------------------------------------------------------//
56//                             Implementations
57//-------------------------------------------------------------------------------//
58
59impl Searchable for Unknown {
60    type SearchMatches = UnknownMatches;
61
62    fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> UnknownMatches {
63        let mut matches = UnknownMatches::new(file_path);
64
65        match matching_mode {
66            MatchingMode::Regex(regex) => {
67
68                // We can assume that, if the original regex was valid, this one is too.
69                let regex = RegexBuilder::new(regex.as_str()).case_insensitive(!case_sensitive).build().unwrap();
70                for match_data in regex.find_iter(self.data()) {
71                    matches.matches.push(
72                        UnknownMatch::new(
73                            match_data.start(),
74                            match_data.end() - match_data.start(),
75                        )
76                    );
77                }
78            }
79
80            MatchingMode::Pattern(regex) => {
81                let regex = regex.as_ref().map(|regex| RegexBuilder::new(regex.as_str()).case_insensitive(!case_sensitive).build().unwrap());
82
83                if self.data().len() > pattern.len() {
84                    for (start, length) in &find_in_bytes(self.data(), pattern, case_sensitive, &regex) {
85                        matches.matches.push(UnknownMatch::new(*start, *length));
86                    }
87                }
88            }
89        }
90
91        matches
92    }
93}
94
95impl Replaceable for Unknown {
96
97    fn replace(&mut self, _pattern: &str, replace_pattern: &str, _case_sensitive: bool, _matching_mode: &MatchingMode, search_matches: &UnknownMatches) -> bool {
98        let mut edited = false;
99
100        // NOTE: Due to changes in index positions, we need to do this in reverse.
101        // Otherwise we may cause one edit to generate invalid indexes for the next matches.
102        for search_match in search_matches.matches().iter().rev() {
103            edited |= search_match.replace(replace_pattern, self.data_mut());
104        }
105
106        edited
107    }
108}
109
110impl UnknownMatches {
111
112    /// This function creates a new `UnknownMatches` for the provided path.
113    pub fn new(path: &str) -> Self {
114        Self {
115            path: path.to_owned(),
116            matches: vec![],
117            source: SearchSource::default(),
118            container_name: String::new(),
119        }
120    }
121}
122
123impl UnknownMatch {
124
125    /// This function creates a new `UnknownMatch` with the provided data.
126    pub fn new(pos: usize, len: usize) -> Self {
127        Self {
128            pos,
129            len,
130        }
131    }
132
133    /// This function replaces all the matches in the provided data.
134    fn replace(&self, replace_pattern: &str, data: &mut Vec<u8>) -> bool {
135        replace_match_bytes(replace_pattern, self.pos, self.len, data)
136    }
137}