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 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) {
929 let matches = files.par_iter_mut()
930 .filter_map(|file| {
931 if search_on.anim && file.file_type() == FileType::Anim {
932 None
944 } else if search_on.anim_fragment_battle && file.file_type() == FileType::AnimFragmentBattle {
945 if let Ok(RFileDecoded::AnimFragmentBattle(data)) = file.decode(&extra_data, false, true).transpose().unwrap() {
946 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
947 result.set_source(source.clone());
948 result.set_container_name(file.container_name().clone().unwrap_or_default());
949 if !result.matches().is_empty() {
950 Some((None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
951 } else {
952 None
953 }
954 } else {
955 None
956 }
957 } else if search_on.anim_pack && file.file_type() == FileType::AnimPack {
958 None
970 } else if search_on.anims_table && file.file_type() == FileType::AnimsTable {
971 None
983 } else if search_on.atlas && file.file_type() == FileType::Atlas {
984 if let Ok(RFileDecoded::Atlas(data)) = file.decode(&None, false, true).transpose().unwrap() {
985 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
986 result.set_source(source.clone());
987 result.set_container_name(file.container_name().clone().unwrap_or_default());
988 if !result.matches().is_empty() {
989 Some((None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
990 } else {
991 None
992 }
993 } else {
994 None
995 }
996 } else if search_on.audio && file.file_type() == FileType::Audio {
997 None
1009 } else if search_on.bmd && file.file_type() == FileType::BMD {
1010 None
1022 } else if search_on.db && file.file_type() == FileType::DB {
1023 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
1024 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1025 result.set_source(source.clone());
1026 result.set_container_name(file.container_name().clone().unwrap_or_default());
1027 if !result.matches().is_empty() {
1028 Some((None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None))
1029 } else {
1030 None
1031 }
1032 } else {
1033 None
1034 }
1035 } else if search_on.esf && file.file_type() == FileType::ESF {
1036 None
1048 } else if search_on.group_formations && file.file_type() == FileType::GroupFormations {
1049 None
1061 } else if search_on.image && file.file_type() == FileType::Image {
1062 None
1074 } else if search_on.loc && file.file_type() == FileType::Loc {
1075 if let Ok(RFileDecoded::Loc(table)) = file.decoded() {
1076 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1077 result.set_source(source.clone());
1078 result.set_container_name(file.container_name().clone().unwrap_or_default());
1079 if !result.matches().is_empty() {
1080 Some((None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None))
1081 } else {
1082 None
1083 }
1084 } else {
1085 None
1086 }
1087 } else if search_on.matched_combat && file.file_type() == FileType::MatchedCombat {
1088 None
1100 } else if search_on.pack && file.file_type() == FileType::Pack {
1101 None
1113 } else if search_on.portrait_settings && file.file_type() == FileType::PortraitSettings {
1114 if let Ok(RFileDecoded::PortraitSettings(data)) = file.decode(&None, false, true).transpose().unwrap() {
1115 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1116 result.set_source(source.clone());
1117 result.set_container_name(file.container_name().clone().unwrap_or_default());
1118 if !result.matches().is_empty() {
1119 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None))
1120 } else {
1121 None
1122 }
1123 } else {
1124 None
1125 }
1126 } else if search_on.rigid_model && file.file_type() == FileType::RigidModel {
1127 if let Ok(RFileDecoded::RigidModel(data)) = file.decode(&None, false, true).transpose().unwrap() {
1128 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1129 result.set_source(source.clone());
1130 result.set_container_name(file.container_name().clone().unwrap_or_default());
1131 if !result.matches().is_empty() {
1132 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None))
1133 } else {
1134 None
1135 }
1136 } else {
1137 None
1138 }
1139 } else if search_on.sound_bank && file.file_type() == FileType::SoundBank {
1140 None
1152 } else if search_on.text && file.file_type() == FileType::Text {
1153 if let Ok(RFileDecoded::Text(text)) = file.decode(&None, false, true).transpose().unwrap() {
1154 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1155 result.set_source(source.clone());
1156 result.set_container_name(file.container_name().clone().unwrap_or_default());
1157 if !result.matches().is_empty() {
1158 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1159 } else {
1160 None
1161 }
1162 } else {
1163 None
1164 }
1165 } else if search_on.text && file.file_type() == FileType::VMD {
1166 if let Ok(RFileDecoded::VMD(text)) = file.decode(&None, false, true).transpose().unwrap() {
1167 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1168 result.set_source(source.clone());
1169 result.set_container_name(file.container_name().clone().unwrap_or_default());
1170 if !result.matches().is_empty() {
1171 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1172 } else {
1173 None
1174 }
1175 } else {
1176 None
1177 }
1178 } else if search_on.text && file.file_type() == FileType::WSModel {
1179 if let Ok(RFileDecoded::WSModel(text)) = file.decode(&None, false, true).transpose().unwrap() {
1180 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1181 result.set_source(source.clone());
1182 result.set_container_name(file.container_name().clone().unwrap_or_default());
1183 if !result.matches().is_empty() {
1184 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1185 } else {
1186 None
1187 }
1188 } else {
1189 None
1190 }
1191 } else if search_on.uic && file.file_type() == FileType::UIC {
1192 None
1204 } else if search_on.unit_variant && file.file_type() == FileType::UnitVariant {
1205 if let Ok(RFileDecoded::UnitVariant(data)) = file.decode(&None, false, true).transpose().unwrap() {
1206 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1207 result.set_source(source.clone());
1208 result.set_container_name(file.container_name().clone().unwrap_or_default());
1209 if !result.matches().is_empty() {
1210 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None))
1211 } else {
1212 None
1213 }
1214 } else {
1215 None
1216 }
1217 } else if search_on.unknown && file.file_type() == FileType::Unknown {
1218 if let Ok(RFileDecoded::Unknown(data)) = file.decode(&None, false, true).transpose().unwrap() {
1219 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1220 result.set_source(source.clone());
1221 result.set_container_name(file.container_name().clone().unwrap_or_default());
1222 if !result.matches().is_empty() {
1223 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None))
1224 } else {
1225 None
1226 }
1227 } else {
1228 None
1229 }
1230 } else if search_on.video && file.file_type() == FileType::Video {
1231 None
1243 } else {
1244 None
1245 }
1246 }
1247 ).collect::<Vec<(
1248 Option<UnknownMatches>, Option<AnimFragmentBattleMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<AtlasMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>,
1249 Option<UnknownMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<PortraitSettingsMatches>,
1250 Option<RigidModelMatches>, Option<UnknownMatches>, Option<TextMatches>, Option<UnknownMatches>, Option<UnitVariantMatches>, Option<UnknownMatches>, Option<UnknownMatches>
1251 )>>();
1252
1253 self.anim = matches.iter().filter_map(|x| x.0.clone()).collect::<Vec<_>>();
1254 self.anim_fragment_battle = matches.iter().filter_map(|x| x.1.clone()).collect::<Vec<_>>();
1255 self.anim_pack = matches.iter().filter_map(|x| x.2.clone()).collect::<Vec<_>>();
1256 self.anims_table = matches.iter().filter_map(|x| x.3.clone()).collect::<Vec<_>>();
1257 self.atlas = matches.iter().filter_map(|x| x.4.clone()).collect::<Vec<_>>();
1258 self.audio = matches.iter().filter_map(|x| x.5.clone()).collect::<Vec<_>>();
1259 self.bmd = matches.iter().filter_map(|x| x.6.clone()).collect::<Vec<_>>();
1260 self.db = matches.iter().filter_map(|x| x.7.clone()).collect::<Vec<_>>();
1261 self.esf = matches.iter().filter_map(|x| x.8.clone()).collect::<Vec<_>>();
1262 self.group_formations = matches.iter().filter_map(|x| x.9.clone()).collect::<Vec<_>>();
1263 self.image = matches.iter().filter_map(|x| x.10.clone()).collect::<Vec<_>>();
1264 self.loc = matches.iter().filter_map(|x| x.11.clone()).collect::<Vec<_>>();
1265 self.matched_combat = matches.iter().filter_map(|x| x.12.clone()).collect::<Vec<_>>();
1266 self.pack = matches.iter().filter_map(|x| x.13.clone()).collect::<Vec<_>>();
1267 self.portrait_settings = matches.iter().filter_map(|x| x.14.clone()).collect::<Vec<_>>();
1268 self.rigid_model = matches.iter().filter_map(|x| x.15.clone()).collect::<Vec<_>>();
1269 self.sound_bank = matches.iter().filter_map(|x| x.16.clone()).collect::<Vec<_>>();
1270 self.text = matches.iter().filter_map(|x| x.17.clone()).collect::<Vec<_>>();
1271 self.uic = matches.iter().filter_map(|x| x.18.clone()).collect::<Vec<_>>();
1272 self.unit_variant = matches.iter().filter_map(|x| x.19.clone()).collect::<Vec<_>>();
1273 self.unknown = matches.iter().filter_map(|x| x.20.clone()).collect::<Vec<_>>();
1274 self.video = matches.iter().filter_map(|x| x.21.clone()).collect::<Vec<_>>();
1275 }
1276
1277 pub fn extend(&mut self, other: Matches) {
1279 self.anim.extend(other.anim);
1280 self.anim_fragment_battle.extend(other.anim_fragment_battle);
1281 self.anim_pack.extend(other.anim_pack);
1282 self.anims_table.extend(other.anims_table);
1283 self.atlas.extend(other.atlas);
1284 self.audio.extend(other.audio);
1285 self.bmd.extend(other.bmd);
1286 self.db.extend(other.db);
1287 self.esf.extend(other.esf);
1288 self.group_formations.extend(other.group_formations);
1289 self.image.extend(other.image);
1290 self.loc.extend(other.loc);
1291 self.matched_combat.extend(other.matched_combat);
1292 self.pack.extend(other.pack);
1293 self.portrait_settings.extend(other.portrait_settings);
1294 self.rigid_model.extend(other.rigid_model);
1295 self.sound_bank.extend(other.sound_bank);
1296 self.text.extend(other.text);
1297 self.uic.extend(other.uic);
1298 self.unit_variant.extend(other.unit_variant);
1299 self.unknown.extend(other.unknown);
1300 self.video.extend(other.video);
1301 }
1303}
1304
1305impl Default for MatchingMode {
1306 fn default() -> Self {
1307 Self::Pattern(None)
1308 }
1309}
1310
1311fn 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 {
1316
1317 if current_data.get(start..end).is_some() {
1319 match matching_mode {
1320 MatchingMode::Regex(regex) => {
1321 if let Some(match_regex) = regex.find(¤t_data[start..end]) {
1322 if match_regex.start() == 0 && match_regex.end() == end - start {
1323 current_data.replace_range(start..end, replace_pattern);
1324 }
1325 }
1326 },
1327
1328 MatchingMode::Pattern(regex) => {
1329 let pattern = if case_sensitive || regex.is_some() {
1330 pattern.to_owned()
1331 } else {
1332 pattern.to_lowercase()
1333 };
1334
1335 if let Some((start_new, end_new, _)) = find_in_string(¤t_data[start..end], &pattern, case_sensitive, regex).first() {
1336 if *start_new == 0 && *end_new == end - start {
1337 current_data.replace_range(start..end, replace_pattern);
1338 }
1339 }
1340 }
1341 }
1342 }
1343
1344 previous_data != *current_data
1345}
1346
1347fn replace_match_bytes(replace_pattern: &str, start: usize, len: usize, data: &mut Vec<u8>) -> bool {
1348 let old_data = data[start..start + len].to_vec();
1349 data.splice(start..start + len, replace_pattern.as_bytes().to_vec());
1350 old_data != data[start..start + len]
1351}
1352
1353fn find_in_string(value: &str, pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<Regex>) -> Vec<(usize, usize, String)> {
1354 if case_sensitive {
1355 value.match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), pat.to_owned())).collect()
1356 } else if let Some(regex) = case_insensitive_regex {
1357 regex.find_iter(value).map(|m| (m.start(), m.end(), m.as_str().to_string())).collect()
1358 } else {
1359 value.to_lowercase().match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), value[start..start + pat.len()].to_string())).collect()
1360 }
1361}
1362
1363fn find_in_bytes(value: &[u8], pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<regex::bytes::Regex>) -> Vec<(usize, usize)> {
1364 if case_sensitive {
1365 let length = pattern.len();
1366 (0..value.len() - length)
1367 .filter_map(|index| if &value[index..index + length] == pattern.as_bytes() { Some((index, length)) } else { None })
1368 .collect()
1369
1370 } else if let Some(regex) = case_insensitive_regex {
1371 regex.find_iter(value).map(|m| (m.start(), m.len())).collect()
1372 } else {
1373 let pattern = pattern.as_bytes().to_ascii_lowercase();
1374 let value = value.to_ascii_lowercase();
1375 let length = pattern.len();
1376 (0..value.len() - length)
1377 .filter_map(|index| if value[index..index + length] == pattern { Some((index, length)) } else { None })
1378 .collect() }
1379}