1use getset::*;
81use regex::{RegexBuilder, Regex};
82use rayon::prelude::*;
83use serde_derive::{Deserialize, Serialize};
84
85use std::collections::BTreeMap;
86
87use rpfm_lib::error::{Result, RLibError};
88use rpfm_lib::files::{Container, ContainerPath, DecodeableExtraData, FileType, pack::Pack, RFile, RFileDecoded};
89use rpfm_lib::games::{GameInfo, VanillaDBTableNameLogic};
90use rpfm_lib::schema::Schema;
91
92use crate::dependencies::Dependencies;
93
94use self::anim_fragment_battle::AnimFragmentBattleMatches;
96use self::atlas::AtlasMatches;
99use self::portrait_settings::PortraitSettingsMatches;
107use self::rigid_model::RigidModelMatches;
108use self::table::TableMatches;
110use self::text::TextMatches;
111use self::unit_variant::UnitVariantMatches;
113use self::unknown::UnknownMatches;
114use self::schema::SchemaMatches;
116
117pub mod anim_fragment_battle;
119pub mod atlas;
122pub mod portrait_settings;
130pub mod rigid_model;
131pub mod table;
133pub mod text;
134pub mod unit_variant;
136pub mod unknown;
137pub mod schema;
139
140pub trait Searchable {
154 type SearchMatches;
156
157 fn search(&self, file_path: &str, pattern_to_search: &str, case_sensitive: bool, matching_mode: &MatchingMode) -> Self::SearchMatches;
170}
171
172pub trait Replaceable: Searchable {
177
178 fn replace(&mut self, pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_matches: &Self::SearchMatches) -> bool;
198}
199
200#[derive(Default, Debug, Clone, Getters, MutGetters, Setters, Serialize, Deserialize)]
218#[getset(get = "pub", get_mut = "pub", set = "pub")]
219pub struct GlobalSearch {
220
221 pattern: String,
223
224 replace_text: String,
229
230 case_sensitive: bool,
234
235 use_regex: bool,
240
241 sources: Vec<SearchSource>,
243
244 search_on: SearchOn,
246
247 matches: Matches,
249
250 game_key: String,
254}
255
256#[derive(Debug, Clone)]
261pub enum MatchingMode {
262 Regex(Regex),
266 Pattern(Option<Regex>),
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
278pub enum MatchHolder {
279 Anim(UnknownMatches),
281 AnimFragmentBattle(AnimFragmentBattleMatches),
283 AnimPack(UnknownMatches),
285 AnimsTable(UnknownMatches),
287 Atlas(AtlasMatches),
289 Audio(UnknownMatches),
291 Bmd(UnknownMatches),
293 Db(TableMatches),
295 Esf(UnknownMatches),
297 GroupFormations(UnknownMatches),
299 Image(UnknownMatches),
301 Loc(TableMatches),
303 MatchedCombat(UnknownMatches),
305 Pack(UnknownMatches),
307 PortraitSettings(PortraitSettingsMatches),
309 RigidModel(RigidModelMatches),
311 SoundBank(UnknownMatches),
313 Text(TextMatches),
315 Uic(UnknownMatches),
317 UnitVariant(UnitVariantMatches),
319 Unknown(UnknownMatches),
321 Video(UnknownMatches),
323 Schema(SchemaMatches),
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub enum SearchSource {
332 Pack(String),
334 ParentFiles,
336 GameFiles,
338 AssKitFiles,
340}
341
342impl Default for SearchSource {
343 fn default() -> Self {
344 Self::Pack(String::new())
345 }
346}
347
348#[derive(Default, Debug, Clone, Getters, Setters, Serialize, Deserialize)]
353#[getset(get = "pub", set = "pub")]
354pub struct SearchOn {
355 anim: bool,
356 anim_fragment_battle: bool,
357 anim_pack: bool,
358 anims_table: bool,
359 atlas: bool,
360 audio: bool,
361 bmd: bool,
362 db: bool,
363 esf: bool,
364 group_formations: bool,
365 image: bool,
366 loc: bool,
367 matched_combat: bool,
368 pack: bool,
369 portrait_settings: bool,
370 rigid_model: bool,
371 sound_bank: bool,
372 text: bool,
373 uic: bool,
374 unit_variant: bool,
375 unknown: bool,
376 video: bool,
377 schema: bool,
378}
379
380#[derive(Default, Debug, Clone, Getters, Serialize, Deserialize)]
382#[getset(get = "pub")]
383pub struct Matches {
384 anim: Vec<UnknownMatches>,
385 anim_fragment_battle: Vec<AnimFragmentBattleMatches>,
386 anim_pack: Vec<UnknownMatches>,
387 anims_table: Vec<UnknownMatches>,
388 atlas: Vec<AtlasMatches>,
389 audio: Vec<UnknownMatches>,
390 bmd: Vec<UnknownMatches>,
391 db: Vec<TableMatches>,
392 esf: Vec<UnknownMatches>,
393 group_formations: Vec<UnknownMatches>,
394 image: Vec<UnknownMatches>,
395 loc: Vec<TableMatches>,
396 matched_combat: Vec<UnknownMatches>,
397 pack: Vec<UnknownMatches>,
398 portrait_settings: Vec<PortraitSettingsMatches>,
399 rigid_model: Vec<RigidModelMatches>,
400 sound_bank: Vec<UnknownMatches>,
401 text: Vec<TextMatches>,
402 uic: Vec<UnknownMatches>,
403 unit_variant: Vec<UnitVariantMatches>,
404 unknown: Vec<UnknownMatches>,
405 video: Vec<UnknownMatches>,
406 schema: SchemaMatches,
407}
408
409impl GlobalSearch {
414
415 pub fn search(&mut self, game_info: &GameInfo, schema: &Schema, packs: &mut BTreeMap<String, Pack>, dependencies: &mut Dependencies, update_paths: &[ContainerPath]) {
417
418 if self.pattern.is_empty() { return }
420
421 let matching_mode = if self.use_regex {
423 match RegexBuilder::new(&self.pattern).case_insensitive(!self.case_sensitive).build() {
424 Ok(regex) => MatchingMode::Regex(regex),
425 Err(_) => MatchingMode::Pattern(RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern)))
426 .case_insensitive(!self.case_sensitive)
427 .build()
428 .ok()
429 ),
430 }
431 } else {
432 match RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern))).case_insensitive(!self.case_sensitive).build() {
433 Ok(regex) => MatchingMode::Pattern(Some(regex)),
434 Err(_) => MatchingMode::Pattern(None),
435 }
436 };
437
438 let has_single_pack_source = self.sources.len() == 1 && matches!(self.sources.first(), Some(SearchSource::Pack(_)));
440 let update_paths = if !update_paths.is_empty() && has_single_pack_source {
441 let container_paths = ContainerPath::dedup(update_paths);
442 let raw_paths = container_paths.par_iter()
443 .flat_map(|container_path| packs.values().flat_map(|pack| pack.paths_raw_from_container_path(container_path)).collect::<Vec<_>>())
444 .collect::<Vec<_>>();
445
446 self.matches_mut().retain_paths(&raw_paths);
447
448 container_paths
449 }
450
451 else {
453 self.matches = Matches::default();
454
455 vec![]
456 };
457
458 self.matches.schema = SchemaMatches::default();
460
461 let pattern_original = self.pattern.to_owned();
462 if !self.case_sensitive {
463 self.pattern = self.pattern.to_lowercase();
464 }
465
466 let pattern = self.pattern.to_owned();
467 let case_sensitive = self.case_sensitive;
468 let search_on = self.search_on().clone();
469 let files_to_search = self.search_on().types_to_search();
470
471 let mut extra_data = DecodeableExtraData::default();
472 extra_data.set_game_info(Some(game_info));
473 let extra_data = Some(extra_data);
474
475 let sources = self.sources.clone();
477
478 for source in &sources {
479 let mut temp_matches = Matches::default();
480
481 match source {
482 SearchSource::Pack(key) => {
483 if let Some(pack) = packs.get_mut(key) {
484 let mut files: Vec<&mut RFile> = if !update_paths.is_empty() {
485 pack.files_by_type_and_paths_mut(&files_to_search, &update_paths, false)
486 } else {
487 pack.files_by_type_mut(&files_to_search)
488 };
489
490 temp_matches.find_matches(&pattern, case_sensitive, &matching_mode, &search_on, &mut files, schema, extra_data.clone(), source);
491 }
492 }
493 SearchSource::ParentFiles => {
494 let files = dependencies.files_by_types_mut(&files_to_search, false, true);
495 temp_matches.find_matches(&pattern, case_sensitive, &matching_mode, &search_on, &mut files.into_values().collect::<Vec<_>>(), schema, extra_data.clone(), source);
496 },
497 SearchSource::GameFiles => {
498 let files = dependencies.files_by_types_mut(&files_to_search, true, false);
499 temp_matches.find_matches(&pattern, case_sensitive, &matching_mode, &search_on, &mut files.into_values().collect::<Vec<_>>(), schema, extra_data.clone(), source);
500 },
501
502 SearchSource::AssKitFiles => {
504 if self.search_on.db {
505 temp_matches.db = dependencies.asskit_only_db_tables()
506 .par_iter()
507 .filter_map(|(table_name, table)| {
508 let file_name = match game_info.vanilla_db_table_name_logic() {
509 VanillaDBTableNameLogic::FolderName => table_name.to_owned(),
510 VanillaDBTableNameLogic::DefaultName(ref default_name) => default_name.to_owned()
511 };
512
513 let path = format!("db/{table_name}/{file_name}");
514 let mut result = table.search(&path, &self.pattern, self.case_sensitive, &matching_mode);
515 result.set_source(source.clone());
516 if !result.matches().is_empty() {
517 Some(result)
518 } else {
519 None
520 }
521 }
522 ).collect();
523 }
524 },
525 }
526
527 self.matches.extend(temp_matches);
528 }
529
530 if search_on.schema {
532 self.matches.schema = schema.search("", &pattern, case_sensitive, &matching_mode);
533 }
534
535 self.pattern = pattern_original;
537 }
538
539 pub fn clear(&mut self) {
541 *self = Self::default();
542 }
543
544 pub fn replace_possible(&self, matches: &[MatchHolder]) -> Result<()> {
546 let patterns_same_lenght = self.pattern.len() == self.replace_text.len();
547
548 if matches.iter().any(|m| match m {
550 MatchHolder::Anim(_) => false,
551 MatchHolder::AnimFragmentBattle(_) => false,
552 MatchHolder::AnimPack(_) => false,
553 MatchHolder::AnimsTable(_) => false,
554 MatchHolder::Atlas(_) => false,
555 MatchHolder::Audio(_) => false,
556 MatchHolder::Bmd(_) => false,
557 MatchHolder::Db(_) => false,
558 MatchHolder::Esf(_) => false,
559 MatchHolder::GroupFormations(_) => false,
560 MatchHolder::Image(_) => false,
561 MatchHolder::Loc(_) => false,
562 MatchHolder::MatchedCombat(_) => false,
563 MatchHolder::Pack(_) => false,
564 MatchHolder::PortraitSettings(_) => false,
565 MatchHolder::RigidModel(_) => self.use_regex || !patterns_same_lenght,
566 MatchHolder::Schema(_) => false,
567 MatchHolder::SoundBank(_) => false,
568 MatchHolder::Text(_) => false,
569 MatchHolder::Uic(_) => false,
570 MatchHolder::UnitVariant(_) => false,
571 MatchHolder::Unknown(_) => self.use_regex || !patterns_same_lenght,
572 MatchHolder::Video(_) => false,
573 }) {
574 Err(RLibError::GlobalSearchReplaceRequiresSameLengthAndNotRegex)
575 } else {
576 Ok(())
577 }
578 }
579
580 pub fn replace(&mut self, game_info: &GameInfo, schema: &Schema, packs: &mut BTreeMap<String, Pack>, dependencies: &mut Dependencies, matches: &[MatchHolder]) -> Result<Vec<ContainerPath>> {
584 let mut edited_paths = vec![];
585
586 if self.pattern.is_empty() {
588 return Ok(edited_paths)
589 }
590
591 if !self.sources.iter().any(|s| matches!(s, SearchSource::Pack(_))) {
593 return Ok(edited_paths)
594 }
595
596 self.replace_possible(matches)?;
598
599 let mut extra_data = DecodeableExtraData::default();
600 extra_data.set_game_info(Some(game_info));
601 let extra_data = Some(extra_data);
602
603 let matching_mode = if self.use_regex {
605 match RegexBuilder::new(&self.pattern).case_insensitive(!self.case_sensitive).build() {
606 Ok(regex) => MatchingMode::Regex(regex),
607 Err(_) => MatchingMode::Pattern(RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern)))
608 .case_insensitive(!self.case_sensitive)
609 .build()
610 .ok()
611 ),
612 }
613 } else {
614 match RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern))).case_insensitive(!self.case_sensitive).build() {
615 Ok(regex) => MatchingMode::Pattern(Some(regex)),
616 Err(_) => MatchingMode::Pattern(None),
617 }
618 };
619
620 for match_file in matches {
622 match match_file {
623 MatchHolder::Anim(_) => continue,
624 MatchHolder::AnimFragmentBattle(search_matches) => {
625 let container_path = ContainerPath::File(search_matches.path().to_string());
626 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
627 if let Some(file) = file.get_mut(0) {
628
629 let _ = file.decode(&extra_data, true, false);
631 if let Ok(decoded) = file.decoded_mut() {
632 let edited = match decoded {
633 RFileDecoded::AnimFragmentBattle(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
634 _ => unimplemented!(),
635 };
636
637 if edited {
638 edited_paths.push(container_path);
639 }
640 }
641 }
642 },
643 MatchHolder::AnimPack(_) => continue,
644 MatchHolder::AnimsTable(_) => continue,
645 MatchHolder::Atlas(search_matches) => {
646 let container_path = ContainerPath::File(search_matches.path().to_string());
647 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
648 if let Some(file) = file.get_mut(0) {
649
650 let _ = file.decode(&None, true, false);
652 if let Ok(decoded) = file.decoded_mut() {
653 let edited = match decoded {
654 RFileDecoded::Atlas(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
655 _ => unimplemented!(),
656 };
657
658 if edited {
659 edited_paths.push(container_path);
660 }
661 }
662 }
663 },
664
665 MatchHolder::Audio(_) => continue,
666 MatchHolder::Bmd(_) => continue,
667
668 MatchHolder::Db(search_matches) => {
669 let container_path = ContainerPath::File(search_matches.path().to_string());
670 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
671 if let Some(file) = file.get_mut(0) {
672 if let Ok(decoded) = file.decoded_mut() {
673 let edited = match decoded {
674 RFileDecoded::DB(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
675 _ => unimplemented!(),
676 };
677
678 if edited {
679 edited_paths.push(container_path);
680 }
681 }
682 }
683 },
684
685 MatchHolder::Esf(_) => continue,
686 MatchHolder::GroupFormations(_) => continue,
687 MatchHolder::Image(_) => continue,
688 MatchHolder::Loc(search_matches) => {
689 let container_path = ContainerPath::File(search_matches.path().to_string());
690 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
691 if let Some(file) = file.get_mut(0) {
692 if let Ok(decoded) = file.decoded_mut() {
693 let edited = match decoded {
694 RFileDecoded::Loc(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
695 _ => unimplemented!(),
696 };
697
698 if edited {
699 edited_paths.push(container_path);
700 }
701 }
702 }
703 },
704
705 MatchHolder::MatchedCombat(_) => continue,
706 MatchHolder::Pack(_) => continue,
707 MatchHolder::PortraitSettings(search_matches) => {
708 let container_path = ContainerPath::File(search_matches.path().to_string());
709 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
710 if let Some(file) = file.get_mut(0) {
711
712 let _ = file.decode(&None, true, false);
714 if let Ok(decoded) = file.decoded_mut() {
715 let edited = match decoded {
716 RFileDecoded::PortraitSettings(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
717 _ => unimplemented!(),
718 };
719
720 if edited {
721 edited_paths.push(container_path);
722 }
723 }
724 }
725 },
726
727 MatchHolder::RigidModel(search_matches) => {
728 let container_path = ContainerPath::File(search_matches.path().to_string());
729 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
730 if let Some(file) = file.get_mut(0) {
731
732 let _ = file.decode(&None, true, false);
734 if let Ok(decoded) = file.decoded_mut() {
735 let edited = match decoded {
736 RFileDecoded::RigidModel(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
737 _ => unimplemented!(),
738 };
739
740 if edited {
741 edited_paths.push(container_path);
742 }
743 }
744 }
745 },
746
747 MatchHolder::SoundBank(_) => continue,
748 MatchHolder::Text(search_matches) => {
749 let container_path = ContainerPath::File(search_matches.path().to_string());
750 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
751 if let Some(file) = file.get_mut(0) {
752
753 let _ = file.decode(&None, true, false);
755 if let Ok(decoded) = file.decoded_mut() {
756
757 let mut search_matches = search_matches.clone();
759 search_matches.matches_mut().par_sort_unstable_by(|a, b| {
760 if a.row() == b.row() {
761 a.start().cmp(b.start())
762 } else {
763 a.row().cmp(b.row())
764 }
765 });
766
767 let edited = match decoded {
768 RFileDecoded::Text(text) |
769 RFileDecoded::VMD(text) |
770 RFileDecoded::WSModel(text) => text.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, &search_matches),
771 _ => unimplemented!(),
772 };
773
774 if edited {
775 edited_paths.push(container_path);
776 }
777 }
778 }
779 },
780
781 MatchHolder::Uic(_) => continue,
782 MatchHolder::UnitVariant(search_matches) => {
783 let container_path = ContainerPath::File(search_matches.path().to_string());
784 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
785 if let Some(file) = file.get_mut(0) {
786
787 let _ = file.decode(&None, true, false);
789 if let Ok(decoded) = file.decoded_mut() {
790 let edited = match decoded {
791 RFileDecoded::UnitVariant(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
792 _ => unimplemented!(),
793 };
794
795 if edited {
796 edited_paths.push(container_path);
797 }
798 }
799 }
800 },
801
802 MatchHolder::Unknown(search_matches) => {
803 let container_path = ContainerPath::File(search_matches.path().to_string());
804 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
805 if let Some(file) = file.get_mut(0) {
806
807 let _ = file.decode(&None, true, false);
809 if let Ok(decoded) = file.decoded_mut() {
810 let edited = match decoded {
811 RFileDecoded::Unknown(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
812 _ => unimplemented!(),
813 };
814
815 if edited {
816 edited_paths.push(container_path);
817 }
818 }
819 }
820 },
821 MatchHolder::Video(_) => continue,
822
823 MatchHolder::Schema(_) => continue,
825 }
826 }
827
828 self.search(game_info, schema, packs, dependencies, &edited_paths);
830
831 Ok(edited_paths)
833 }
834
835 pub fn replace_all(&mut self, game_info: &GameInfo, schema: &Schema, packs: &mut BTreeMap<String, Pack>, dependencies: &mut Dependencies) -> Result<Vec<ContainerPath>> {
836 let mut matches = vec![];
837
838 matches.extend(self.matches.anim.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
839 matches.extend(self.matches.anim_fragment_battle.iter().map(|x| MatchHolder::AnimFragmentBattle(x.clone())).collect::<Vec<_>>());
840 matches.extend(self.matches.anim_pack.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
841 matches.extend(self.matches.anims_table.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
842 matches.extend(self.matches.atlas.iter().map(|x| MatchHolder::Atlas(x.clone())).collect::<Vec<_>>());
843 matches.extend(self.matches.audio.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
844 matches.extend(self.matches.bmd.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
845 matches.extend(self.matches.db.iter().map(|x| MatchHolder::Db(x.clone())).collect::<Vec<_>>());
846 matches.extend(self.matches.esf.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
847 matches.extend(self.matches.group_formations.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
848 matches.extend(self.matches.image.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
849 matches.extend(self.matches.loc.iter().map(|x| MatchHolder::Loc(x.clone())).collect::<Vec<_>>());
850 matches.extend(self.matches.matched_combat.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
851 matches.extend(self.matches.pack.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
852 matches.extend(self.matches.portrait_settings.iter().map(|x| MatchHolder::PortraitSettings(x.clone())).collect::<Vec<_>>());
853 matches.extend(self.matches.rigid_model.iter().map(|x| MatchHolder::RigidModel(x.clone())).collect::<Vec<_>>());
854 matches.extend(self.matches.sound_bank.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
855 matches.extend(self.matches.text.iter().map(|x| MatchHolder::Text(x.clone())).collect::<Vec<_>>());
856 matches.extend(self.matches.uic.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
857 matches.extend(self.matches.unit_variant.iter().map(|x| MatchHolder::UnitVariant(x.clone())).collect::<Vec<_>>());
858 matches.extend(self.matches.unknown.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
859 matches.extend(self.matches.video.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
860
861 self.replace(game_info, schema, packs, dependencies, &matches)
862 }
863}
864
865impl SearchOn {
866 pub fn types_to_search(&self) -> Vec<FileType> {
867 let mut types = vec![];
868
869 if *self.anim() { types.push(FileType::Anim); }
870 if *self.anim_fragment_battle() { types.push(FileType::AnimFragmentBattle); }
871 if *self.anim_pack() { types.push(FileType::AnimPack); }
872 if *self.anims_table() { types.push(FileType::AnimsTable); }
873 if *self.atlas() { types.push(FileType::Atlas); }
874 if *self.audio() { types.push(FileType::Audio); }
875 if *self.bmd() { types.push(FileType::BMD); }
876 if *self.db() { types.push(FileType::DB); }
877 if *self.esf() { types.push(FileType::ESF); }
878 if *self.group_formations() { types.push(FileType::GroupFormations); }
879 if *self.image() { types.push(FileType::Image); }
880 if *self.loc() { types.push(FileType::Loc); }
881 if *self.matched_combat() { types.push(FileType::MatchedCombat); }
882 if *self.pack() { types.push(FileType::Pack); }
883 if *self.portrait_settings() { types.push(FileType::PortraitSettings); }
884 if *self.rigid_model() { types.push(FileType::RigidModel); }
885 if *self.sound_bank() { types.push(FileType::SoundBank); }
886 if *self.text() {
887 types.push(FileType::Text);
888 types.push(FileType::VMD);
889 types.push(FileType::WSModel);
890 }
891 if *self.uic() { types.push(FileType::UIC); }
892 if *self.unit_variant() { types.push(FileType::UnitVariant); }
893 if *self.unknown() { types.push(FileType::Unknown); }
894 if *self.video() { types.push(FileType::Video); }
895
896 types
897 }
898}
899
900impl Matches {
901 pub fn retain_paths(&mut self, paths: &[String]) {
902 for path in paths {
903 self.anim.retain(|x| x.path() != path);
904 self.anim_fragment_battle.retain(|x| x.path() != path);
905 self.anim_pack.retain(|x| x.path() != path);
906 self.anims_table.retain(|x| x.path() != path);
907 self.atlas.retain(|x| x.path() != path);
908 self.audio.retain(|x| x.path() != path);
909 self.bmd.retain(|x| x.path() != path);
910 self.db.retain(|x| x.path() != path);
911 self.esf.retain(|x| x.path() != path);
912 self.group_formations.retain(|x| x.path() != path);
913 self.image.retain(|x| x.path() != path);
914 self.loc.retain(|x| x.path() != path);
915 self.matched_combat.retain(|x| x.path() != path);
916 self.pack.retain(|x| x.path() != path);
917 self.portrait_settings.retain(|x| x.path() != path);
918 self.rigid_model.retain(|x| x.path() != path);
919 self.sound_bank.retain(|x| x.path() != path);
920 self.text.retain(|x| x.path() != path);
921 self.uic.retain(|x| x.path() != path);
922 self.unit_variant.retain(|x| x.path() != path);
923 self.unknown.retain(|x| x.path() != path);
924 self.video.retain(|x| x.path() != path);
925 }
926 }
927
928 #[allow(clippy::too_many_arguments)]
929 pub fn find_matches(&mut self, pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, search_on: &SearchOn, files: &mut Vec<&mut RFile>, _schema: &Schema, extra_data: Option<DecodeableExtraData>, source: &SearchSource) {
930 let matches = files.par_iter_mut()
931 .filter_map(|file| {
932 if search_on.anim && file.file_type() == FileType::Anim {
933 None
945 } else if search_on.anim_fragment_battle && file.file_type() == FileType::AnimFragmentBattle {
946 if let Ok(RFileDecoded::AnimFragmentBattle(data)) = file.decode(&extra_data, false, true).transpose().unwrap() {
947 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
948 result.set_source(source.clone());
949 result.set_container_name(file.container_name().clone().unwrap_or_default());
950 if !result.matches().is_empty() {
951 Some((None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
952 } else {
953 None
954 }
955 } else {
956 None
957 }
958 } else if search_on.anim_pack && file.file_type() == FileType::AnimPack {
959 None
971 } else if search_on.anims_table && file.file_type() == FileType::AnimsTable {
972 None
984 } else if search_on.atlas && file.file_type() == FileType::Atlas {
985 if let Ok(RFileDecoded::Atlas(data)) = file.decode(&None, false, true).transpose().unwrap() {
986 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
987 result.set_source(source.clone());
988 result.set_container_name(file.container_name().clone().unwrap_or_default());
989 if !result.matches().is_empty() {
990 Some((None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
991 } else {
992 None
993 }
994 } else {
995 None
996 }
997 } else if search_on.audio && file.file_type() == FileType::Audio {
998 None
1010 } else if search_on.bmd && file.file_type() == FileType::BMD {
1011 None
1023 } else if search_on.db && file.file_type() == FileType::DB {
1024 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
1025 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1026 result.set_source(source.clone());
1027 result.set_container_name(file.container_name().clone().unwrap_or_default());
1028 if !result.matches().is_empty() {
1029 Some((None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None))
1030 } else {
1031 None
1032 }
1033 } else {
1034 None
1035 }
1036 } else if search_on.esf && file.file_type() == FileType::ESF {
1037 None
1049 } else if search_on.group_formations && file.file_type() == FileType::GroupFormations {
1050 None
1062 } else if search_on.image && file.file_type() == FileType::Image {
1063 None
1075 } else if search_on.loc && file.file_type() == FileType::Loc {
1076 if let Ok(RFileDecoded::Loc(table)) = file.decoded() {
1077 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1078 result.set_source(source.clone());
1079 result.set_container_name(file.container_name().clone().unwrap_or_default());
1080 if !result.matches().is_empty() {
1081 Some((None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None))
1082 } else {
1083 None
1084 }
1085 } else {
1086 None
1087 }
1088 } else if search_on.matched_combat && file.file_type() == FileType::MatchedCombat {
1089 None
1101 } else if search_on.pack && file.file_type() == FileType::Pack {
1102 None
1114 } else if search_on.portrait_settings && file.file_type() == FileType::PortraitSettings {
1115 if let Ok(RFileDecoded::PortraitSettings(data)) = file.decode(&None, false, true).transpose().unwrap() {
1116 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1117 result.set_source(source.clone());
1118 result.set_container_name(file.container_name().clone().unwrap_or_default());
1119 if !result.matches().is_empty() {
1120 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None))
1121 } else {
1122 None
1123 }
1124 } else {
1125 None
1126 }
1127 } else if search_on.rigid_model && file.file_type() == FileType::RigidModel {
1128 if let Ok(RFileDecoded::RigidModel(data)) = file.decode(&None, false, true).transpose().unwrap() {
1129 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1130 result.set_source(source.clone());
1131 result.set_container_name(file.container_name().clone().unwrap_or_default());
1132 if !result.matches().is_empty() {
1133 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None))
1134 } else {
1135 None
1136 }
1137 } else {
1138 None
1139 }
1140 } else if search_on.sound_bank && file.file_type() == FileType::SoundBank {
1141 None
1153 } else if search_on.text && file.file_type() == FileType::Text {
1154 if let Ok(RFileDecoded::Text(text)) = file.decode(&None, false, true).transpose().unwrap() {
1155 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1156 result.set_source(source.clone());
1157 result.set_container_name(file.container_name().clone().unwrap_or_default());
1158 if !result.matches().is_empty() {
1159 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1160 } else {
1161 None
1162 }
1163 } else {
1164 None
1165 }
1166 } else if search_on.text && file.file_type() == FileType::VMD {
1167 if let Ok(RFileDecoded::VMD(text)) = file.decode(&None, false, true).transpose().unwrap() {
1168 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1169 result.set_source(source.clone());
1170 result.set_container_name(file.container_name().clone().unwrap_or_default());
1171 if !result.matches().is_empty() {
1172 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1173 } else {
1174 None
1175 }
1176 } else {
1177 None
1178 }
1179 } else if search_on.text && file.file_type() == FileType::WSModel {
1180 if let Ok(RFileDecoded::WSModel(text)) = file.decode(&None, false, true).transpose().unwrap() {
1181 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1182 result.set_source(source.clone());
1183 result.set_container_name(file.container_name().clone().unwrap_or_default());
1184 if !result.matches().is_empty() {
1185 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1186 } else {
1187 None
1188 }
1189 } else {
1190 None
1191 }
1192 } else if search_on.uic && file.file_type() == FileType::UIC {
1193 None
1205 } else if search_on.unit_variant && file.file_type() == FileType::UnitVariant {
1206 if let Ok(RFileDecoded::UnitVariant(data)) = file.decode(&None, false, true).transpose().unwrap() {
1207 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1208 result.set_source(source.clone());
1209 result.set_container_name(file.container_name().clone().unwrap_or_default());
1210 if !result.matches().is_empty() {
1211 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None))
1212 } else {
1213 None
1214 }
1215 } else {
1216 None
1217 }
1218 } else if search_on.unknown && file.file_type() == FileType::Unknown {
1219 if let Ok(RFileDecoded::Unknown(data)) = file.decode(&None, false, true).transpose().unwrap() {
1220 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1221 result.set_source(source.clone());
1222 result.set_container_name(file.container_name().clone().unwrap_or_default());
1223 if !result.matches().is_empty() {
1224 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None))
1225 } else {
1226 None
1227 }
1228 } else {
1229 None
1230 }
1231 } else if search_on.video && file.file_type() == FileType::Video {
1232 None
1244 } else {
1245 None
1246 }
1247 }
1248 ).collect::<Vec<(
1249 Option<UnknownMatches>, Option<AnimFragmentBattleMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<AtlasMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>,
1250 Option<UnknownMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<PortraitSettingsMatches>,
1251 Option<RigidModelMatches>, Option<UnknownMatches>, Option<TextMatches>, Option<UnknownMatches>, Option<UnitVariantMatches>, Option<UnknownMatches>, Option<UnknownMatches>
1252 )>>();
1253
1254 self.anim = matches.iter().filter_map(|x| x.0.clone()).collect::<Vec<_>>();
1255 self.anim_fragment_battle = matches.iter().filter_map(|x| x.1.clone()).collect::<Vec<_>>();
1256 self.anim_pack = matches.iter().filter_map(|x| x.2.clone()).collect::<Vec<_>>();
1257 self.anims_table = matches.iter().filter_map(|x| x.3.clone()).collect::<Vec<_>>();
1258 self.atlas = matches.iter().filter_map(|x| x.4.clone()).collect::<Vec<_>>();
1259 self.audio = matches.iter().filter_map(|x| x.5.clone()).collect::<Vec<_>>();
1260 self.bmd = matches.iter().filter_map(|x| x.6.clone()).collect::<Vec<_>>();
1261 self.db = matches.iter().filter_map(|x| x.7.clone()).collect::<Vec<_>>();
1262 self.esf = matches.iter().filter_map(|x| x.8.clone()).collect::<Vec<_>>();
1263 self.group_formations = matches.iter().filter_map(|x| x.9.clone()).collect::<Vec<_>>();
1264 self.image = matches.iter().filter_map(|x| x.10.clone()).collect::<Vec<_>>();
1265 self.loc = matches.iter().filter_map(|x| x.11.clone()).collect::<Vec<_>>();
1266 self.matched_combat = matches.iter().filter_map(|x| x.12.clone()).collect::<Vec<_>>();
1267 self.pack = matches.iter().filter_map(|x| x.13.clone()).collect::<Vec<_>>();
1268 self.portrait_settings = matches.iter().filter_map(|x| x.14.clone()).collect::<Vec<_>>();
1269 self.rigid_model = matches.iter().filter_map(|x| x.15.clone()).collect::<Vec<_>>();
1270 self.sound_bank = matches.iter().filter_map(|x| x.16.clone()).collect::<Vec<_>>();
1271 self.text = matches.iter().filter_map(|x| x.17.clone()).collect::<Vec<_>>();
1272 self.uic = matches.iter().filter_map(|x| x.18.clone()).collect::<Vec<_>>();
1273 self.unit_variant = matches.iter().filter_map(|x| x.19.clone()).collect::<Vec<_>>();
1274 self.unknown = matches.iter().filter_map(|x| x.20.clone()).collect::<Vec<_>>();
1275 self.video = matches.iter().filter_map(|x| x.21.clone()).collect::<Vec<_>>();
1276 }
1277
1278 pub fn extend(&mut self, other: Matches) {
1280 self.anim.extend(other.anim);
1281 self.anim_fragment_battle.extend(other.anim_fragment_battle);
1282 self.anim_pack.extend(other.anim_pack);
1283 self.anims_table.extend(other.anims_table);
1284 self.atlas.extend(other.atlas);
1285 self.audio.extend(other.audio);
1286 self.bmd.extend(other.bmd);
1287 self.db.extend(other.db);
1288 self.esf.extend(other.esf);
1289 self.group_formations.extend(other.group_formations);
1290 self.image.extend(other.image);
1291 self.loc.extend(other.loc);
1292 self.matched_combat.extend(other.matched_combat);
1293 self.pack.extend(other.pack);
1294 self.portrait_settings.extend(other.portrait_settings);
1295 self.rigid_model.extend(other.rigid_model);
1296 self.sound_bank.extend(other.sound_bank);
1297 self.text.extend(other.text);
1298 self.uic.extend(other.uic);
1299 self.unit_variant.extend(other.unit_variant);
1300 self.unknown.extend(other.unknown);
1301 self.video.extend(other.video);
1302 }
1304}
1305
1306impl Default for MatchingMode {
1307 fn default() -> Self {
1308 Self::Pattern(None)
1309 }
1310}
1311
1312#[allow(clippy::too_many_arguments)]
1317fn replace_match_string(pattern: &str, replace_pattern: &str, case_sensitive: bool, matching_mode: &MatchingMode, start: usize, end: usize, previous_data: &str, current_data: &mut String) -> bool {
1318
1319 if current_data.get(start..end).is_some() {
1321 match matching_mode {
1322 MatchingMode::Regex(regex) => {
1323 if let Some(match_regex) = regex.find(¤t_data[start..end]) {
1324 if match_regex.start() == 0 && match_regex.end() == end - start {
1325 current_data.replace_range(start..end, replace_pattern);
1326 }
1327 }
1328 },
1329
1330 MatchingMode::Pattern(regex) => {
1331 let pattern = if case_sensitive || regex.is_some() {
1332 pattern.to_owned()
1333 } else {
1334 pattern.to_lowercase()
1335 };
1336
1337 if let Some((start_new, end_new, _)) = find_in_string(¤t_data[start..end], &pattern, case_sensitive, regex).first() {
1338 if *start_new == 0 && *end_new == end - start {
1339 current_data.replace_range(start..end, replace_pattern);
1340 }
1341 }
1342 }
1343 }
1344 }
1345
1346 previous_data != *current_data
1347}
1348
1349fn replace_match_bytes(replace_pattern: &str, start: usize, len: usize, data: &mut Vec<u8>) -> bool {
1350 let old_data = data[start..start + len].to_vec();
1351 data.splice(start..start + len, replace_pattern.as_bytes().to_vec());
1352 old_data != data[start..start + len]
1353}
1354
1355fn find_in_string(value: &str, pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<Regex>) -> Vec<(usize, usize, String)> {
1356 if case_sensitive {
1357 value.match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), pat.to_owned())).collect()
1358 } else if let Some(regex) = case_insensitive_regex {
1359 regex.find_iter(value).map(|m| (m.start(), m.end(), m.as_str().to_string())).collect()
1360 } else {
1361 value.to_lowercase().match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), value[start..start + pat.len()].to_string())).collect()
1362 }
1363}
1364
1365fn find_in_bytes(value: &[u8], pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<regex::bytes::Regex>) -> Vec<(usize, usize)> {
1366 if case_sensitive {
1367 let length = pattern.len();
1368 (0..value.len() - length)
1369 .filter_map(|index| if &value[index..index + length] == pattern.as_bytes() { Some((index, length)) } else { None })
1370 .collect()
1371
1372 } else if let Some(regex) = case_insensitive_regex {
1373 regex.find_iter(value).map(|m| (m.start(), m.len())).collect()
1374 } else {
1375 let pattern = pattern.as_bytes().to_ascii_lowercase();
1376 let value = value.to_ascii_lowercase();
1377 let length = pattern.len();
1378 (0..value.len() - length)
1379 .filter_map(|index| if value[index..index + length] == pattern { Some((index, length)) } else { None })
1380 .collect() }
1381}