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;
90use rpfm_lib::schema::Schema;
91
92use crate::dependencies::{ASSEMBLY_KIT_TABLE_FILE_NAME, 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 path = format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}");
509 let mut result = table.search(&path, &self.pattern, self.case_sensitive, &matching_mode);
510 result.set_source(source.clone());
511 if !result.matches().is_empty() {
512 Some(result)
513 } else {
514 None
515 }
516 }
517 ).collect();
518 }
519 },
520 }
521
522 self.matches.extend(temp_matches);
523 }
524
525 if search_on.schema {
527 self.matches.schema = schema.search("", &pattern, case_sensitive, &matching_mode);
528 }
529
530 self.pattern = pattern_original;
532 }
533
534 pub fn clear(&mut self) {
536 *self = Self::default();
537 }
538
539 pub fn replace_possible(&self, matches: &[MatchHolder]) -> Result<()> {
541 let patterns_same_lenght = self.pattern.len() == self.replace_text.len();
542
543 if matches.iter().any(|m| match m {
545 MatchHolder::Anim(_) => false,
546 MatchHolder::AnimFragmentBattle(_) => false,
547 MatchHolder::AnimPack(_) => false,
548 MatchHolder::AnimsTable(_) => false,
549 MatchHolder::Atlas(_) => false,
550 MatchHolder::Audio(_) => false,
551 MatchHolder::Bmd(_) => false,
552 MatchHolder::Db(_) => false,
553 MatchHolder::Esf(_) => false,
554 MatchHolder::GroupFormations(_) => false,
555 MatchHolder::Image(_) => false,
556 MatchHolder::Loc(_) => false,
557 MatchHolder::MatchedCombat(_) => false,
558 MatchHolder::Pack(_) => false,
559 MatchHolder::PortraitSettings(_) => false,
560 MatchHolder::RigidModel(_) => self.use_regex || !patterns_same_lenght,
561 MatchHolder::Schema(_) => false,
562 MatchHolder::SoundBank(_) => false,
563 MatchHolder::Text(_) => false,
564 MatchHolder::Uic(_) => false,
565 MatchHolder::UnitVariant(_) => false,
566 MatchHolder::Unknown(_) => self.use_regex || !patterns_same_lenght,
567 MatchHolder::Video(_) => false,
568 }) {
569 Err(RLibError::GlobalSearchReplaceRequiresSameLengthAndNotRegex)
570 } else {
571 Ok(())
572 }
573 }
574
575 pub fn replace(&mut self, game_info: &GameInfo, schema: &Schema, packs: &mut BTreeMap<String, Pack>, dependencies: &mut Dependencies, matches: &[MatchHolder]) -> Result<Vec<ContainerPath>> {
579 let mut edited_paths = vec![];
580
581 if self.pattern.is_empty() {
583 return Ok(edited_paths)
584 }
585
586 if !self.sources.iter().any(|s| matches!(s, SearchSource::Pack(_))) {
588 return Ok(edited_paths)
589 }
590
591 self.replace_possible(matches)?;
593
594 let mut extra_data = DecodeableExtraData::default();
595 extra_data.set_game_info(Some(game_info));
596 let extra_data = Some(extra_data);
597
598 let matching_mode = if self.use_regex {
600 match RegexBuilder::new(&self.pattern).case_insensitive(!self.case_sensitive).build() {
601 Ok(regex) => MatchingMode::Regex(regex),
602 Err(_) => MatchingMode::Pattern(RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern)))
603 .case_insensitive(!self.case_sensitive)
604 .build()
605 .ok()
606 ),
607 }
608 } else {
609 match RegexBuilder::new(&format!("(?i){}", regex::escape(&self.pattern))).case_insensitive(!self.case_sensitive).build() {
610 Ok(regex) => MatchingMode::Pattern(Some(regex)),
611 Err(_) => MatchingMode::Pattern(None),
612 }
613 };
614
615 for match_file in matches {
617 match match_file {
618 MatchHolder::Anim(_) => continue,
619 MatchHolder::AnimFragmentBattle(search_matches) => {
620 let container_path = ContainerPath::File(search_matches.path().to_string());
621 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
622 if let Some(file) = file.get_mut(0) {
623
624 let _ = file.decode(&extra_data, true, false);
626 if let Ok(decoded) = file.decoded_mut() {
627 let edited = match decoded {
628 RFileDecoded::AnimFragmentBattle(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
629 _ => unimplemented!(),
630 };
631
632 if edited {
633 edited_paths.push(container_path);
634 }
635 }
636 }
637 },
638 MatchHolder::AnimPack(_) => continue,
639 MatchHolder::AnimsTable(_) => continue,
640 MatchHolder::Atlas(search_matches) => {
641 let container_path = ContainerPath::File(search_matches.path().to_string());
642 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
643 if let Some(file) = file.get_mut(0) {
644
645 let _ = file.decode(&None, true, false);
647 if let Ok(decoded) = file.decoded_mut() {
648 let edited = match decoded {
649 RFileDecoded::Atlas(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
650 _ => unimplemented!(),
651 };
652
653 if edited {
654 edited_paths.push(container_path);
655 }
656 }
657 }
658 },
659
660 MatchHolder::Audio(_) => continue,
661 MatchHolder::Bmd(_) => continue,
662
663 MatchHolder::Db(search_matches) => {
664 let container_path = ContainerPath::File(search_matches.path().to_string());
665 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
666 if let Some(file) = file.get_mut(0) {
667 if let Ok(decoded) = file.decoded_mut() {
668 let edited = match decoded {
669 RFileDecoded::DB(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
670 _ => unimplemented!(),
671 };
672
673 if edited {
674 edited_paths.push(container_path);
675 }
676 }
677 }
678 },
679
680 MatchHolder::Esf(_) => continue,
681 MatchHolder::GroupFormations(_) => continue,
682 MatchHolder::Image(_) => continue,
683 MatchHolder::Loc(search_matches) => {
684 let container_path = ContainerPath::File(search_matches.path().to_string());
685 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
686 if let Some(file) = file.get_mut(0) {
687 if let Ok(decoded) = file.decoded_mut() {
688 let edited = match decoded {
689 RFileDecoded::Loc(table) => table.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
690 _ => unimplemented!(),
691 };
692
693 if edited {
694 edited_paths.push(container_path);
695 }
696 }
697 }
698 },
699
700 MatchHolder::MatchedCombat(_) => continue,
701 MatchHolder::Pack(_) => continue,
702 MatchHolder::PortraitSettings(search_matches) => {
703 let container_path = ContainerPath::File(search_matches.path().to_string());
704 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
705 if let Some(file) = file.get_mut(0) {
706
707 let _ = file.decode(&None, true, false);
709 if let Ok(decoded) = file.decoded_mut() {
710 let edited = match decoded {
711 RFileDecoded::PortraitSettings(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
712 _ => unimplemented!(),
713 };
714
715 if edited {
716 edited_paths.push(container_path);
717 }
718 }
719 }
720 },
721
722 MatchHolder::RigidModel(search_matches) => {
723 let container_path = ContainerPath::File(search_matches.path().to_string());
724 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
725 if let Some(file) = file.get_mut(0) {
726
727 let _ = file.decode(&None, true, false);
729 if let Ok(decoded) = file.decoded_mut() {
730 let edited = match decoded {
731 RFileDecoded::RigidModel(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
732 _ => unimplemented!(),
733 };
734
735 if edited {
736 edited_paths.push(container_path);
737 }
738 }
739 }
740 },
741
742 MatchHolder::SoundBank(_) => continue,
743 MatchHolder::Text(search_matches) => {
744 let container_path = ContainerPath::File(search_matches.path().to_string());
745 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
746 if let Some(file) = file.get_mut(0) {
747
748 let _ = file.decode(&None, true, false);
750 if let Ok(decoded) = file.decoded_mut() {
751
752 let mut search_matches = search_matches.clone();
754 search_matches.matches_mut().par_sort_unstable_by(|a, b| {
755 if a.row() == b.row() {
756 a.start().cmp(b.start())
757 } else {
758 a.row().cmp(b.row())
759 }
760 });
761
762 let edited = match decoded {
763 RFileDecoded::Text(text) |
764 RFileDecoded::VMD(text) |
765 RFileDecoded::WSModel(text) => text.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, &search_matches),
766 _ => unimplemented!(),
767 };
768
769 if edited {
770 edited_paths.push(container_path);
771 }
772 }
773 }
774 },
775
776 MatchHolder::Uic(_) => continue,
777 MatchHolder::UnitVariant(search_matches) => {
778 let container_path = ContainerPath::File(search_matches.path().to_string());
779 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
780 if let Some(file) = file.get_mut(0) {
781
782 let _ = file.decode(&None, true, false);
784 if let Ok(decoded) = file.decoded_mut() {
785 let edited = match decoded {
786 RFileDecoded::UnitVariant(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
787 _ => unimplemented!(),
788 };
789
790 if edited {
791 edited_paths.push(container_path);
792 }
793 }
794 }
795 },
796
797 MatchHolder::Unknown(search_matches) => {
798 let container_path = ContainerPath::File(search_matches.path().to_string());
799 let mut file: Vec<&mut RFile> = packs.values_mut().flat_map(|pack| pack.files_by_path_mut(&container_path, false)).collect();
800 if let Some(file) = file.get_mut(0) {
801
802 let _ = file.decode(&None, true, false);
804 if let Ok(decoded) = file.decoded_mut() {
805 let edited = match decoded {
806 RFileDecoded::Unknown(data) => data.replace(&self.pattern, &self.replace_text, self.case_sensitive, &matching_mode, search_matches),
807 _ => unimplemented!(),
808 };
809
810 if edited {
811 edited_paths.push(container_path);
812 }
813 }
814 }
815 },
816 MatchHolder::Video(_) => continue,
817
818 MatchHolder::Schema(_) => continue,
820 }
821 }
822
823 self.search(game_info, schema, packs, dependencies, &edited_paths);
825
826 Ok(edited_paths)
828 }
829
830 pub fn replace_all(&mut self, game_info: &GameInfo, schema: &Schema, packs: &mut BTreeMap<String, Pack>, dependencies: &mut Dependencies) -> Result<Vec<ContainerPath>> {
831 let mut matches = vec![];
832
833 matches.extend(self.matches.anim.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
834 matches.extend(self.matches.anim_fragment_battle.iter().map(|x| MatchHolder::AnimFragmentBattle(x.clone())).collect::<Vec<_>>());
835 matches.extend(self.matches.anim_pack.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
836 matches.extend(self.matches.anims_table.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
837 matches.extend(self.matches.atlas.iter().map(|x| MatchHolder::Atlas(x.clone())).collect::<Vec<_>>());
838 matches.extend(self.matches.audio.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
839 matches.extend(self.matches.bmd.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
840 matches.extend(self.matches.db.iter().map(|x| MatchHolder::Db(x.clone())).collect::<Vec<_>>());
841 matches.extend(self.matches.esf.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
842 matches.extend(self.matches.group_formations.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
843 matches.extend(self.matches.image.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
844 matches.extend(self.matches.loc.iter().map(|x| MatchHolder::Loc(x.clone())).collect::<Vec<_>>());
845 matches.extend(self.matches.matched_combat.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
846 matches.extend(self.matches.pack.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
847 matches.extend(self.matches.portrait_settings.iter().map(|x| MatchHolder::PortraitSettings(x.clone())).collect::<Vec<_>>());
848 matches.extend(self.matches.rigid_model.iter().map(|x| MatchHolder::RigidModel(x.clone())).collect::<Vec<_>>());
849 matches.extend(self.matches.sound_bank.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
850 matches.extend(self.matches.text.iter().map(|x| MatchHolder::Text(x.clone())).collect::<Vec<_>>());
851 matches.extend(self.matches.uic.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
852 matches.extend(self.matches.unit_variant.iter().map(|x| MatchHolder::UnitVariant(x.clone())).collect::<Vec<_>>());
853 matches.extend(self.matches.unknown.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
854 matches.extend(self.matches.video.iter().map(|x| MatchHolder::Unknown(x.clone())).collect::<Vec<_>>());
855
856 self.replace(game_info, schema, packs, dependencies, &matches)
857 }
858}
859
860impl SearchOn {
861 pub fn types_to_search(&self) -> Vec<FileType> {
862 let mut types = vec![];
863
864 if *self.anim() { types.push(FileType::Anim); }
865 if *self.anim_fragment_battle() { types.push(FileType::AnimFragmentBattle); }
866 if *self.anim_pack() { types.push(FileType::AnimPack); }
867 if *self.anims_table() { types.push(FileType::AnimsTable); }
868 if *self.atlas() { types.push(FileType::Atlas); }
869 if *self.audio() { types.push(FileType::Audio); }
870 if *self.bmd() { types.push(FileType::BMD); }
871 if *self.db() { types.push(FileType::DB); }
872 if *self.esf() { types.push(FileType::ESF); }
873 if *self.group_formations() { types.push(FileType::GroupFormations); }
874 if *self.image() { types.push(FileType::Image); }
875 if *self.loc() { types.push(FileType::Loc); }
876 if *self.matched_combat() { types.push(FileType::MatchedCombat); }
877 if *self.pack() { types.push(FileType::Pack); }
878 if *self.portrait_settings() { types.push(FileType::PortraitSettings); }
879 if *self.rigid_model() { types.push(FileType::RigidModel); }
880 if *self.sound_bank() { types.push(FileType::SoundBank); }
881 if *self.text() {
882 types.push(FileType::Text);
883 types.push(FileType::VMD);
884 types.push(FileType::WSModel);
885 }
886 if *self.uic() { types.push(FileType::UIC); }
887 if *self.unit_variant() { types.push(FileType::UnitVariant); }
888 if *self.unknown() { types.push(FileType::Unknown); }
889 if *self.video() { types.push(FileType::Video); }
890
891 types
892 }
893}
894
895impl Matches {
896 pub fn retain_paths(&mut self, paths: &[String]) {
897 for path in paths {
898 self.anim.retain(|x| x.path() != path);
899 self.anim_fragment_battle.retain(|x| x.path() != path);
900 self.anim_pack.retain(|x| x.path() != path);
901 self.anims_table.retain(|x| x.path() != path);
902 self.atlas.retain(|x| x.path() != path);
903 self.audio.retain(|x| x.path() != path);
904 self.bmd.retain(|x| x.path() != path);
905 self.db.retain(|x| x.path() != path);
906 self.esf.retain(|x| x.path() != path);
907 self.group_formations.retain(|x| x.path() != path);
908 self.image.retain(|x| x.path() != path);
909 self.loc.retain(|x| x.path() != path);
910 self.matched_combat.retain(|x| x.path() != path);
911 self.pack.retain(|x| x.path() != path);
912 self.portrait_settings.retain(|x| x.path() != path);
913 self.rigid_model.retain(|x| x.path() != path);
914 self.sound_bank.retain(|x| x.path() != path);
915 self.text.retain(|x| x.path() != path);
916 self.uic.retain(|x| x.path() != path);
917 self.unit_variant.retain(|x| x.path() != path);
918 self.unknown.retain(|x| x.path() != path);
919 self.video.retain(|x| x.path() != path);
920 }
921 }
922
923 #[allow(clippy::too_many_arguments)]
924 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) {
925 let matches = files.par_iter_mut()
926 .filter_map(|file| {
927 if search_on.anim && file.file_type() == FileType::Anim {
928 None
940 } else if search_on.anim_fragment_battle && file.file_type() == FileType::AnimFragmentBattle {
941 if let Ok(RFileDecoded::AnimFragmentBattle(data)) = file.decode(&extra_data, false, true).transpose().unwrap() {
942 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
943 result.set_source(source.clone());
944 result.set_container_name(file.container_name().clone().unwrap_or_default());
945 if !result.matches().is_empty() {
946 Some((None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
947 } else {
948 None
949 }
950 } else {
951 None
952 }
953 } else if search_on.anim_pack && file.file_type() == FileType::AnimPack {
954 None
966 } else if search_on.anims_table && file.file_type() == FileType::AnimsTable {
967 None
979 } else if search_on.atlas && file.file_type() == FileType::Atlas {
980 if let Ok(RFileDecoded::Atlas(data)) = file.decode(&None, false, true).transpose().unwrap() {
981 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
982 result.set_source(source.clone());
983 result.set_container_name(file.container_name().clone().unwrap_or_default());
984 if !result.matches().is_empty() {
985 Some((None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None))
986 } else {
987 None
988 }
989 } else {
990 None
991 }
992 } else if search_on.audio && file.file_type() == FileType::Audio {
993 None
1005 } else if search_on.bmd && file.file_type() == FileType::BMD {
1006 None
1018 } else if search_on.db && file.file_type() == FileType::DB {
1019 if let Ok(RFileDecoded::DB(table)) = file.decoded() {
1020 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1021 result.set_source(source.clone());
1022 result.set_container_name(file.container_name().clone().unwrap_or_default());
1023 if !result.matches().is_empty() {
1024 Some((None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None, None, None, None, None))
1025 } else {
1026 None
1027 }
1028 } else {
1029 None
1030 }
1031 } else if search_on.esf && file.file_type() == FileType::ESF {
1032 None
1044 } else if search_on.group_formations && file.file_type() == FileType::GroupFormations {
1045 None
1057 } else if search_on.image && file.file_type() == FileType::Image {
1058 None
1070 } else if search_on.loc && file.file_type() == FileType::Loc {
1071 if let Ok(RFileDecoded::Loc(table)) = file.decoded() {
1072 let mut result = table.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1073 result.set_source(source.clone());
1074 result.set_container_name(file.container_name().clone().unwrap_or_default());
1075 if !result.matches().is_empty() {
1076 Some((None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None, None, None, None))
1077 } else {
1078 None
1079 }
1080 } else {
1081 None
1082 }
1083 } else if search_on.matched_combat && file.file_type() == FileType::MatchedCombat {
1084 None
1096 } else if search_on.pack && file.file_type() == FileType::Pack {
1097 None
1109 } else if search_on.portrait_settings && file.file_type() == FileType::PortraitSettings {
1110 if let Ok(RFileDecoded::PortraitSettings(data)) = file.decode(&None, false, true).transpose().unwrap() {
1111 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1112 result.set_source(source.clone());
1113 result.set_container_name(file.container_name().clone().unwrap_or_default());
1114 if !result.matches().is_empty() {
1115 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None, None))
1116 } else {
1117 None
1118 }
1119 } else {
1120 None
1121 }
1122 } else if search_on.rigid_model && file.file_type() == FileType::RigidModel {
1123 if let Ok(RFileDecoded::RigidModel(data)) = file.decode(&None, false, true).transpose().unwrap() {
1124 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1125 result.set_source(source.clone());
1126 result.set_container_name(file.container_name().clone().unwrap_or_default());
1127 if !result.matches().is_empty() {
1128 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None, None, None))
1129 } else {
1130 None
1131 }
1132 } else {
1133 None
1134 }
1135 } else if search_on.sound_bank && file.file_type() == FileType::SoundBank {
1136 None
1148 } else if search_on.text && file.file_type() == FileType::Text {
1149 if let Ok(RFileDecoded::Text(text)) = file.decode(&None, false, true).transpose().unwrap() {
1150 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1151 result.set_source(source.clone());
1152 result.set_container_name(file.container_name().clone().unwrap_or_default());
1153 if !result.matches().is_empty() {
1154 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1155 } else {
1156 None
1157 }
1158 } else {
1159 None
1160 }
1161 } else if search_on.text && file.file_type() == FileType::VMD {
1162 if let Ok(RFileDecoded::VMD(text)) = file.decode(&None, false, true).transpose().unwrap() {
1163 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1164 result.set_source(source.clone());
1165 result.set_container_name(file.container_name().clone().unwrap_or_default());
1166 if !result.matches().is_empty() {
1167 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1168 } else {
1169 None
1170 }
1171 } else {
1172 None
1173 }
1174 } else if search_on.text && file.file_type() == FileType::WSModel {
1175 if let Ok(RFileDecoded::WSModel(text)) = file.decode(&None, false, true).transpose().unwrap() {
1176 let mut result = text.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1177 result.set_source(source.clone());
1178 result.set_container_name(file.container_name().clone().unwrap_or_default());
1179 if !result.matches().is_empty() {
1180 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None, None, None))
1181 } else {
1182 None
1183 }
1184 } else {
1185 None
1186 }
1187 } else if search_on.uic && file.file_type() == FileType::UIC {
1188 None
1200 } else if search_on.unit_variant && file.file_type() == FileType::UnitVariant {
1201 if let Ok(RFileDecoded::UnitVariant(data)) = file.decode(&None, false, true).transpose().unwrap() {
1202 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1203 result.set_source(source.clone());
1204 result.set_container_name(file.container_name().clone().unwrap_or_default());
1205 if !result.matches().is_empty() {
1206 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None, None))
1207 } else {
1208 None
1209 }
1210 } else {
1211 None
1212 }
1213 } else if search_on.unknown && file.file_type() == FileType::Unknown {
1214 if let Ok(RFileDecoded::Unknown(data)) = file.decode(&None, false, true).transpose().unwrap() {
1215 let mut result = data.search(file.path_in_container_raw(), pattern, case_sensitive, matching_mode);
1216 result.set_source(source.clone());
1217 result.set_container_name(file.container_name().clone().unwrap_or_default());
1218 if !result.matches().is_empty() {
1219 Some((None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(result), None))
1220 } else {
1221 None
1222 }
1223 } else {
1224 None
1225 }
1226 } else if search_on.video && file.file_type() == FileType::Video {
1227 None
1239 } else {
1240 None
1241 }
1242 }
1243 ).collect::<Vec<(
1244 Option<UnknownMatches>, Option<AnimFragmentBattleMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<AtlasMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>,
1245 Option<UnknownMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<TableMatches>, Option<UnknownMatches>, Option<UnknownMatches>, Option<PortraitSettingsMatches>,
1246 Option<RigidModelMatches>, Option<UnknownMatches>, Option<TextMatches>, Option<UnknownMatches>, Option<UnitVariantMatches>, Option<UnknownMatches>, Option<UnknownMatches>
1247 )>>();
1248
1249 self.anim = matches.iter().filter_map(|x| x.0.clone()).collect::<Vec<_>>();
1250 self.anim_fragment_battle = matches.iter().filter_map(|x| x.1.clone()).collect::<Vec<_>>();
1251 self.anim_pack = matches.iter().filter_map(|x| x.2.clone()).collect::<Vec<_>>();
1252 self.anims_table = matches.iter().filter_map(|x| x.3.clone()).collect::<Vec<_>>();
1253 self.atlas = matches.iter().filter_map(|x| x.4.clone()).collect::<Vec<_>>();
1254 self.audio = matches.iter().filter_map(|x| x.5.clone()).collect::<Vec<_>>();
1255 self.bmd = matches.iter().filter_map(|x| x.6.clone()).collect::<Vec<_>>();
1256 self.db = matches.iter().filter_map(|x| x.7.clone()).collect::<Vec<_>>();
1257 self.esf = matches.iter().filter_map(|x| x.8.clone()).collect::<Vec<_>>();
1258 self.group_formations = matches.iter().filter_map(|x| x.9.clone()).collect::<Vec<_>>();
1259 self.image = matches.iter().filter_map(|x| x.10.clone()).collect::<Vec<_>>();
1260 self.loc = matches.iter().filter_map(|x| x.11.clone()).collect::<Vec<_>>();
1261 self.matched_combat = matches.iter().filter_map(|x| x.12.clone()).collect::<Vec<_>>();
1262 self.pack = matches.iter().filter_map(|x| x.13.clone()).collect::<Vec<_>>();
1263 self.portrait_settings = matches.iter().filter_map(|x| x.14.clone()).collect::<Vec<_>>();
1264 self.rigid_model = matches.iter().filter_map(|x| x.15.clone()).collect::<Vec<_>>();
1265 self.sound_bank = matches.iter().filter_map(|x| x.16.clone()).collect::<Vec<_>>();
1266 self.text = matches.iter().filter_map(|x| x.17.clone()).collect::<Vec<_>>();
1267 self.uic = matches.iter().filter_map(|x| x.18.clone()).collect::<Vec<_>>();
1268 self.unit_variant = matches.iter().filter_map(|x| x.19.clone()).collect::<Vec<_>>();
1269 self.unknown = matches.iter().filter_map(|x| x.20.clone()).collect::<Vec<_>>();
1270 self.video = matches.iter().filter_map(|x| x.21.clone()).collect::<Vec<_>>();
1271 }
1272
1273 pub fn extend(&mut self, other: Matches) {
1275 self.anim.extend(other.anim);
1276 self.anim_fragment_battle.extend(other.anim_fragment_battle);
1277 self.anim_pack.extend(other.anim_pack);
1278 self.anims_table.extend(other.anims_table);
1279 self.atlas.extend(other.atlas);
1280 self.audio.extend(other.audio);
1281 self.bmd.extend(other.bmd);
1282 self.db.extend(other.db);
1283 self.esf.extend(other.esf);
1284 self.group_formations.extend(other.group_formations);
1285 self.image.extend(other.image);
1286 self.loc.extend(other.loc);
1287 self.matched_combat.extend(other.matched_combat);
1288 self.pack.extend(other.pack);
1289 self.portrait_settings.extend(other.portrait_settings);
1290 self.rigid_model.extend(other.rigid_model);
1291 self.sound_bank.extend(other.sound_bank);
1292 self.text.extend(other.text);
1293 self.uic.extend(other.uic);
1294 self.unit_variant.extend(other.unit_variant);
1295 self.unknown.extend(other.unknown);
1296 self.video.extend(other.video);
1297 }
1299}
1300
1301impl Default for MatchingMode {
1302 fn default() -> Self {
1303 Self::Pattern(None)
1304 }
1305}
1306
1307#[allow(clippy::too_many_arguments)]
1312fn 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 {
1313
1314 if current_data.get(start..end).is_some() {
1316 match matching_mode {
1317 MatchingMode::Regex(regex) => {
1318 if let Some(match_regex) = regex.find(¤t_data[start..end]) {
1319 if match_regex.start() == 0 && match_regex.end() == end - start {
1320 current_data.replace_range(start..end, replace_pattern);
1321 }
1322 }
1323 },
1324
1325 MatchingMode::Pattern(regex) => {
1326 let pattern = if case_sensitive || regex.is_some() {
1327 pattern.to_owned()
1328 } else {
1329 pattern.to_lowercase()
1330 };
1331
1332 if let Some((start_new, end_new, _)) = find_in_string(¤t_data[start..end], &pattern, case_sensitive, regex).first() {
1333 if *start_new == 0 && *end_new == end - start {
1334 current_data.replace_range(start..end, replace_pattern);
1335 }
1336 }
1337 }
1338 }
1339 }
1340
1341 previous_data != *current_data
1342}
1343
1344fn replace_match_bytes(replace_pattern: &str, start: usize, len: usize, data: &mut Vec<u8>) -> bool {
1345 let old_data = data[start..start + len].to_vec();
1346 data.splice(start..start + len, replace_pattern.as_bytes().to_vec());
1347 old_data != data[start..start + len]
1348}
1349
1350fn find_in_string(value: &str, pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<Regex>) -> Vec<(usize, usize, String)> {
1351 if case_sensitive {
1352 value.match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), pat.to_owned())).collect()
1353 } else if let Some(regex) = case_insensitive_regex {
1354 regex.find_iter(value).map(|m| (m.start(), m.end(), m.as_str().to_string())).collect()
1355 } else {
1356 value.to_lowercase().match_indices(&pattern).map(|(start, pat)| (start, start + pat.len(), value[start..start + pat.len()].to_string())).collect()
1357 }
1358}
1359
1360fn find_in_bytes(value: &[u8], pattern: &str, case_sensitive: bool, case_insensitive_regex: &Option<regex::bytes::Regex>) -> Vec<(usize, usize)> {
1361 if case_sensitive {
1362 let length = pattern.len();
1363 (0..value.len() - length)
1364 .filter_map(|index| if &value[index..index + length] == pattern.as_bytes() { Some((index, length)) } else { None })
1365 .collect()
1366
1367 } else if let Some(regex) = case_insensitive_regex {
1368 regex.find_iter(value).map(|m| (m.start(), m.len())).collect()
1369 } else {
1370 let pattern = pattern.as_bytes().to_ascii_lowercase();
1371 let value = value.to_ascii_lowercase();
1372 let length = pattern.len();
1373 (0..value.len() - length)
1374 .filter_map(|index| if value[index..index + length] == pattern { Some((index, length)) } else { None })
1375 .collect() }
1376}