rpfm_extensions/search/unit_variant.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::unit_variant::UnitVariant;
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 UnitVariant File.
23#[derive(Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
24#[getset(get = "pub", get_mut = "pub", set = "pub")]
25pub struct UnitVariantMatches {
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<UnitVariantMatch>,
40}
41
42/// This struct represents a match within an UnitVariant File.
43#[derive(Debug, Clone, Eq, PartialEq, Getters, MutGetters, Serialize, Deserialize)]
44#[getset(get = "pub", get_mut = "pub")]
45pub struct UnitVariantMatch {
46
47 /// The index of the entry in question in the UnitVariant file. Not sure if the ids are unique, so we use the index.
48 entry: usize,
49
50 /// If the match corresponds to the name.
51 name: bool,
52
53 /// If the match corresponds to a variant value. We have their index and a bool for each value.
54 variant: Option<(usize, bool, bool)>,
55
56 /// Byte where the match starts.
57 start: usize,
58
59 /// Byte where the match ends.
60 end: usize,
61
62 /// Matched data.
63 text: String,
64}
65
66//-------------------------------------------------------------------------------//
67// Implementations
68//-------------------------------------------------------------------------------//
69
70impl Searchable for UnitVariant {
71 type SearchMatches = UnitVariantMatches;
72
73 fn search(&self, file_path: &str, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> UnitVariantMatches {
74 let mut matches = UnitVariantMatches::new(file_path);
75
76 match matching_mode {
77 MatchingMode::Regex(regex) => {
78 for (index, data) in self.categories().iter().enumerate() {
79 for entry_match in regex.find_iter(data.name()) {
80 matches.matches.push(
81 UnitVariantMatch::new(
82 index,
83 true,
84 None,
85 entry_match.start(),
86 entry_match.end(),
87 data.name().to_owned()
88 )
89 );
90 }
91
92 for (vindex, variant) in data.variants().iter().enumerate() {
93 for entry_match in regex.find_iter(variant.mesh_file()) {
94 matches.matches.push(
95 UnitVariantMatch::new(
96 index,
97 false,
98 Some((vindex, true, false)),
99 entry_match.start(),
100 entry_match.end(),
101 variant.mesh_file().to_owned()
102 )
103 );
104 }
105
106 for entry_match in regex.find_iter(variant.texture_folder()) {
107 matches.matches.push(
108 UnitVariantMatch::new(
109 index,
110 false,
111 Some((vindex, false, true)),
112 entry_match.start(),
113 entry_match.end(),
114 variant.texture_folder().to_owned()
115 )
116 );
117 }
118 }
119 }
120 }
121
122 MatchingMode::Pattern(regex) => {
123 for (index, data) in self.categories().iter().enumerate() {
124 for (start, end, _) in &find_in_string(data.name(), pattern, case_sensitive, regex) {
125 matches.matches.push(
126 UnitVariantMatch::new(
127 index,
128 true,
129 None,
130 *start,
131 *end,
132 data.name().to_owned()
133 )
134 );
135 }
136
137 for (vindex, variant) in data.variants().iter().enumerate() {
138 for (start, end, _) in &find_in_string(variant.mesh_file(), pattern, case_sensitive, regex) {
139 matches.matches.push(
140 UnitVariantMatch::new(
141 index,
142 false,
143 Some((vindex, true, false)),
144 *start,
145 *end,
146 variant.mesh_file().to_owned()
147 )
148 );
149 }
150
151 for (start, end, _) in &find_in_string(variant.texture_folder(), pattern, case_sensitive, regex) {
152 matches.matches.push(
153 UnitVariantMatch::new(
154 index,
155 false,
156 Some((vindex, false, true)),
157 *start,
158 *end,
159 variant.texture_folder().to_owned()
160 )
161 );
162 }
163 }
164 }
165 }
166 }
167
168 matches
169 }
170}
171
172impl Replaceable for UnitVariant {
173
174 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &UnitVariantMatches) -> bool {
175 let mut edited = false;
176
177 // NOTE: Due to changes in index positions, we need to do this in reverse.
178 // Otherwise we may cause one edit to generate invalid indexes for the next matches.
179 for search_match in search_matches.matches().iter().rev() {
180 edited |= search_match.replace(pattern, replace_pattern, case_sensitive, matching_mode, self);
181 }
182
183 edited
184 }
185}
186
187impl UnitVariantMatches {
188
189 /// This function creates a new `UnitVariantMatches` for the provided path.
190 pub fn new(path: &str) -> Self {
191 Self {
192 path: path.to_owned(),
193 matches: vec![],
194 source: SearchSource::default(),
195 container_name: String::new(),
196 }
197 }
198}
199
200impl UnitVariantMatch {
201
202 /// This function creates a new `UnitVariantMatch` with the provided data.
203 pub fn new(entry: usize, name: bool, variant: Option<(usize, bool, bool)>, start: usize, end: usize, text: String) -> Self {
204 Self {
205 entry,
206 name,
207 variant,
208 start,
209 end,
210 text
211 }
212 }
213
214 /// This function replaces all the matches in the provided data.
215 fn replace(&self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, data: &mut UnitVariant) -> bool {
216 let mut edited = false;
217
218 if let Some(entry) = data.categories_mut().get_mut(self.entry) {
219
220 // 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.
221 let (previous_data, current_data) = {
222 if self.name {
223 (entry.name().to_owned(), entry.name_mut())
224 } else if let Some((vindex, mesh_file, texture_folder)) = self.variant {
225 match entry.variants_mut().get_mut(vindex) {
226 Some(variant) => {
227 if mesh_file {
228 (variant.mesh_file().to_owned(), variant.mesh_file_mut())
229 } else if texture_folder {
230 (variant.texture_folder().to_owned(), variant.texture_folder_mut())
231 } else {
232 return false;
233 }
234 }
235 None => return false,
236 }
237 }
238
239 // This is an error.
240 else {
241 return false
242 }
243 };
244
245 edited = replace_match_string(pattern, replace_pattern, case_sensitive, matching_mode, self.start, self.end, &previous_data, current_data);
246 }
247
248 edited
249 }
250}