Skip to main content

rpfm_extensions/diagnostics/
portrait_settings.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Module with the structs and functions specific for `PortraitSettings` diagnostics.
12
13use getset::{Getters, MutGetters};
14use serde_derive::{Serialize, Deserialize};
15
16use std::collections::{HashMap, HashSet};
17use std::{fmt, fmt::Display};
18
19use rpfm_lib::files::{RFile, RFileDecoded};
20
21use crate::dependencies::Dependencies;
22use crate::diagnostics::*;
23
24//-------------------------------------------------------------------------------//
25//                              Enums & Structs
26//-------------------------------------------------------------------------------//
27
28/// This struct contains the results of a PortraitSettings diagnostic.
29#[derive(Debug, Clone, Default, Getters, MutGetters, Serialize, Deserialize)]
30#[getset(get = "pub", get_mut = "pub")]
31pub struct PortraitSettingsDiagnostic {
32    path: String,
33    pack: String,
34    results: Vec<PortraitSettingsDiagnosticReport>
35}
36
37/// This struct defines an individual PortraitSettings diagnostic result.
38#[derive(Debug, Clone, Getters, MutGetters, Serialize, Deserialize)]
39#[getset(get = "pub", get_mut = "pub")]
40pub struct PortraitSettingsDiagnosticReport {
41    report_type: PortraitSettingsDiagnosticReportType,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub enum PortraitSettingsDiagnosticReportType {
46    DatacoredPortraitSettings,
47    InvalidArtSetId(String),
48    InvalidVariantFilename(String, String, bool, bool),
49    FileDiffuseNotFoundForVariant(String, String, bool, bool, String),
50    FileMask1NotFoundForVariant(String, String, bool, bool, String),
51    FileMask2NotFoundForVariant(String, String, bool, bool, String),
52    FileMask3NotFoundForVariant(String, String, bool, bool, String),
53}
54
55//-------------------------------------------------------------------------------//
56//                             Implementations
57//-------------------------------------------------------------------------------//
58
59impl PortraitSettingsDiagnosticReport {
60    pub fn new(report_type: PortraitSettingsDiagnosticReportType) -> Self {
61        Self {
62            report_type
63        }
64    }
65}
66
67impl DiagnosticReport for PortraitSettingsDiagnosticReport {
68    fn message(&self) -> String {
69        match &self.report_type {
70            PortraitSettingsDiagnosticReportType::DatacoredPortraitSettings => "Datacored Portrait Settings file.".to_string(),
71            PortraitSettingsDiagnosticReportType::InvalidArtSetId(art_set_id) => format!("Invalid Art Set Id '{art_set_id}' in Portrait Settings file."),
72            PortraitSettingsDiagnosticReportType::InvalidVariantFilename(art_set_id, variant_filename, _, _) => format!("Invalid Variant Filename '{variant_filename}' for Art Set Id '{art_set_id}'. "),
73            PortraitSettingsDiagnosticReportType::FileDiffuseNotFoundForVariant(art_set_id, variant_filename, _, _, path) => format!("File not found for Art Set Id '{art_set_id}', Variant Filename '{variant_filename}', File Diffuse '{path}'."),
74            PortraitSettingsDiagnosticReportType::FileMask1NotFoundForVariant(art_set_id, variant_filename, _, _, path) => format!("File not found for Art Set Id '{art_set_id}', Variant Filename '{variant_filename}', File Mask 1 '{path}'."),
75            PortraitSettingsDiagnosticReportType::FileMask2NotFoundForVariant(art_set_id, variant_filename, _, _, path) => format!("File not found for Art Set Id '{art_set_id}', Variant Filename '{variant_filename}', File Mask 2 '{path}'."),
76            PortraitSettingsDiagnosticReportType::FileMask3NotFoundForVariant(art_set_id, variant_filename, _, _, path) => format!("File not found for Art Set Id '{art_set_id}', Variant Filename '{variant_filename}', File Mask 3 '{path}'."),
77        }
78    }
79
80    fn level(&self) -> DiagnosticLevel {
81        match self.report_type {
82            PortraitSettingsDiagnosticReportType::DatacoredPortraitSettings => DiagnosticLevel::Warning,
83            PortraitSettingsDiagnosticReportType::InvalidArtSetId(_) => DiagnosticLevel::Warning,
84            PortraitSettingsDiagnosticReportType::InvalidVariantFilename(_, _, _, _) => DiagnosticLevel::Warning,
85            PortraitSettingsDiagnosticReportType::FileDiffuseNotFoundForVariant(_, _, _, _, _) => DiagnosticLevel::Warning,
86            PortraitSettingsDiagnosticReportType::FileMask1NotFoundForVariant(_, _, _, _, _) => DiagnosticLevel::Warning,
87            PortraitSettingsDiagnosticReportType::FileMask2NotFoundForVariant(_, _, _, _, _) => DiagnosticLevel::Warning,
88            PortraitSettingsDiagnosticReportType::FileMask3NotFoundForVariant(_, _, _, _, _) => DiagnosticLevel::Warning,
89        }
90    }
91}
92
93impl Display for PortraitSettingsDiagnosticReportType {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        Display::fmt(match self {
96            Self::DatacoredPortraitSettings => "DatacoredPortraitSettings",
97            Self::InvalidArtSetId(_) => "InvalidArtSetId",
98            Self::InvalidVariantFilename(_, _, _, _) => "InvalidVariantFilename",
99            Self::FileDiffuseNotFoundForVariant(_, _, _, _, _) => "FileDiffuseNotFoundForVariant",
100            Self::FileMask1NotFoundForVariant(_, _, _, _, _) => "FileMask1NotFoundForVariant",
101            Self::FileMask2NotFoundForVariant(_, _, _, _, _) => "FileMask2NotFoundForVariant",
102            Self::FileMask3NotFoundForVariant(_, _, _, _, _) => "FileMask3NotFoundForVariant",
103        }, f)
104    }
105}
106
107impl PortraitSettingsDiagnostic {
108    pub fn new(path: &str, pack: &str) -> Self {
109        Self {
110            path: path.to_owned(),
111            pack: pack.to_owned(),
112            results: vec![],
113        }
114    }
115
116    /// This function takes care of checking for PortraitSettings-Related for errors.
117    pub fn check(
118        pack_key: &str,
119        file: &RFile,
120        art_set_ids: &HashSet<String>,
121        variant_filenames: &HashSet<String>,
122        dependencies: &Dependencies,
123        global_ignored_diagnostics: &[String],
124        ignored_fields: &[String],
125        ignored_diagnostics: &HashSet<String>,
126        ignored_diagnostics_for_fields: &HashMap<String, Vec<String>>,
127        local_path_list: &HashMap<String, Vec<String>>,
128    ) -> Option<DiagnosticType> {
129        if let Ok(RFileDecoded::PortraitSettings(portrait_settings)) = file.decoded() {
130            let mut diagnostic = Self::new(file.path_in_container_raw(), pack_key);
131
132            // Disabled, as some games seem to only load their portrait_settings files named as portrait_settings.bin.
133            /*if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("DatacoredPortraitSettings"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) && dependencies.file_exists(file.path_in_container_raw(), true, false, false)  {
134                let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::DatacoredPortraitSettings);
135                diagnostic.results_mut().push(result);
136            }*/
137
138            for entry in portrait_settings.entries() {
139                if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("InvalidArtSetId"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) && art_set_ids.get(entry.id()).is_none()  {
140                    let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::InvalidArtSetId(entry.id().to_owned()));
141                    diagnostic.results_mut().push(result);
142                }
143
144                for variant in entry.variants() {
145                    if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("InvalidVariantFilename"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) && variant_filenames.get(variant.filename()).is_none()  {
146                        let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::InvalidVariantFilename(entry.id().to_owned(), variant.filename().to_owned(), *variant.politician(), *variant.faction_leader()));
147                        diagnostic.results_mut().push(result);
148                    }
149
150                    if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("FileDiffuseNotFoundForVariant"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) &&
151                        (
152                            local_path_list.get(&variant.file_diffuse().to_lowercase()).is_none() &&
153                            !dependencies.file_exists(variant.file_diffuse(), true, true, true)
154                        ) && !variant.file_diffuse().is_empty() {
155                        let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::FileDiffuseNotFoundForVariant(entry.id().to_owned(), variant.filename().to_owned(), *variant.politician(), *variant.faction_leader(), variant.file_diffuse().to_owned()));
156                        diagnostic.results_mut().push(result);
157                    }
158
159                    if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("FileMask1NotFoundForVariant"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) &&
160                        (
161                            local_path_list.get(&variant.file_mask_1().to_lowercase()).is_none() &&
162                            !dependencies.file_exists(variant.file_mask_1(), true, true, true)
163                        ) && !variant.file_mask_1().is_empty() {
164                        let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::FileMask1NotFoundForVariant(entry.id().to_owned(), variant.filename().to_owned(), *variant.politician(), *variant.faction_leader(), variant.file_mask_1().to_owned()));
165                        diagnostic.results_mut().push(result);
166                    }
167
168                    if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("FileMask2NotFoundForVariant"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) &&
169                        (
170                            local_path_list.get(&variant.file_mask_2().to_lowercase()).is_none() &&
171                            !dependencies.file_exists(variant.file_mask_2(), true, true, true)
172                        ) && !variant.file_mask_2().is_empty() {
173                        let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::FileMask2NotFoundForVariant(entry.id().to_owned(), variant.filename().to_owned(), *variant.politician(), *variant.faction_leader(), variant.file_mask_2().to_owned()));
174                        diagnostic.results_mut().push(result);
175                    }
176
177                    if !Diagnostics::ignore_diagnostic(global_ignored_diagnostics, None, Some("FileMask3NotFoundForVariant"), ignored_fields, ignored_diagnostics, ignored_diagnostics_for_fields) &&
178                        (
179                            local_path_list.get(&variant.file_mask_3().to_lowercase()).is_none() &&
180                            !dependencies.file_exists(variant.file_mask_3(), true, true, true)
181                        ) && !variant.file_mask_3().is_empty() {
182                        let result = PortraitSettingsDiagnosticReport::new(PortraitSettingsDiagnosticReportType::FileMask3NotFoundForVariant(entry.id().to_owned(), variant.filename().to_owned(), *variant.politician(), *variant.faction_leader(), variant.file_mask_3().to_owned()));
183                        diagnostic.results_mut().push(result);
184                    }
185                }
186            }
187
188            if !diagnostic.results().is_empty() {
189                Some(DiagnosticType::PortraitSettings(diagnostic))
190            } else { None }
191        } else { None }
192    }
193}