Skip to main content

rpfm_server/state/
dependencies.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//! Game selection and dependencies operations.
12
13use std::path::Path;
14
15use anyhow::{anyhow, Result};
16
17use rpfm_extensions::dependencies::Dependencies;
18
19use rpfm_ipc::helpers::DependenciesInfo;
20use rpfm_ipc::settings_keys::SECONDARY_PATH;
21
22use rpfm_lib::compression::CompressionFormat;
23
24use rpfm_telemetry::info;
25
26use rpfm_ipc::settings::{dependencies_cache_path, Settings};
27
28use super::{SessionState, load_schema, loaded_schema, parent_pack_names};
29
30impl SessionState {
31
32    /// Changes the selected game, loading its schema and adapting the open packs to it.
33    ///
34    /// If the dependencies are rebuilt, their tables are left undecoded: call [`Self::decode_dependency_tables`] after this.
35    ///
36    /// # Arguments
37    ///
38    /// * `game_key` - Key of the game to select.
39    /// * `rebuild_dependencies` - If the dependencies are rebuilt for the new game.
40    /// * `settings` - Settings, to find the game's install folder.
41    /// * `disable_uuid_regeneration` - If tables keep their GUID when re-encoded for the new schema.
42    ///
43    /// # Returns
44    ///
45    /// The compression format of the first open pack, and the info of the dependencies if they were rebuilt.
46    pub fn set_game_selected(&mut self, game_key: &str, rebuild_dependencies: bool, settings: &Settings, disable_uuid_regeneration: bool) -> Result<(CompressionFormat, Option<DependenciesInfo>)> {
47        info!("Setting game selected.");
48        let game_changed = self.game.key() != game_key || !self.first_game_change_done;
49        self.game = self.supported_games.game(game_key)
50            .ok_or_else(|| anyhow!("The selected game is not supported!"))?
51            .clone();
52
53        // We need to make sure the compression format is valid for our game for all open packs.
54        for pack in self.packs.values_mut() {
55            let current_cf = pack.compression_format();
56            if current_cf != CompressionFormat::None && !self.game.compression_formats_supported().contains(&current_cf) {
57                let new_cf = self.game.compression_formats_supported().first().copied().unwrap_or(CompressionFormat::None);
58                pack.set_compression_format(new_cf, &self.game);
59            }
60        }
61
62        load_schema(&mut self.schema, &mut self.packs, &self.game, disable_uuid_regeneration);
63
64        let dependencies_info = if rebuild_dependencies {
65            let cache_path = dependencies_cache_path()?.join(self.game.dependencies_cache_file_name());
66            let cache_path = if game_changed { Some(&*cache_path) } else { None };
67
68            // Failed loads keep what could be loaded, and their error is reported by the session status.
69            let _ = self.load_dependencies(cache_path, false, settings);
70
71            Some(DependenciesInfo::new(&self.dependencies, self.game.vanilla_db_table_name_logic()))
72        } else {
73            None
74        };
75
76        let compression_format = self.packs.values().next()
77            .map(|pack| pack.compression_format())
78            .unwrap_or(CompressionFormat::None);
79
80        // For all open packs, change their id to match the one of the new game.
81        let game_version_number = self.game.game_version_number(&settings.path_buf(self.game.key()));
82        for pack in self.packs.values_mut() {
83            if !pack.disk_file_path().is_empty() {
84                let pfh_file_type = *pack.header().pfh_file_type();
85                pack.header_mut().set_pfh_version(self.game.pfh_version_by_file_type(pfh_file_type));
86
87                if let Some(version_number) = game_version_number {
88                    pack.set_game_version(version_number);
89                }
90            }
91        }
92
93        self.first_game_change_done = true;
94        info!("Switching game selected done.");
95
96        Ok((compression_format, dependencies_info))
97    }
98
99    /// Decodes the tables of the dependencies with the loaded schema.
100    pub fn decode_dependency_tables(&mut self) {
101        self.dependencies.decode_tables(&self.schema);
102    }
103
104    /// Generates the dependencies cache of the selected game from its files and Assembly Kit, then loads it.
105    ///
106    /// # Arguments
107    ///
108    /// * `settings` - Settings, to find the game's install folder and Assembly Kit.
109    /// * `ignore_game_files_in_ak` - If tables in the game files are skipped when reading the Assembly Kit.
110    ///
111    /// # Returns
112    ///
113    /// The info of the new dependencies.
114    pub fn generate_dependencies_cache(&mut self, settings: &Settings, ignore_game_files_in_ak: bool) -> Result<DependenciesInfo> {
115        let game_path = settings.path_buf(self.game.key());
116        if !game_path.is_dir() {
117            return Err(anyhow!("Game Path not configured. Go to <i>'PackFile/Settings'</i> and configure it."));
118        }
119
120        let asskit_path = settings.assembly_kit_path(&self.game).ok();
121        let mut cache = Dependencies::generate_dependencies_cache(&self.schema, &self.game, &game_path, &asskit_path, ignore_game_files_in_ak)?;
122
123        let cache_path = dependencies_cache_path()?.join(self.game.dependencies_cache_file_name());
124        cache.save(&cache_path)?;
125
126        let _ = self.load_dependencies(Some(&cache_path), true, settings);
127        Ok(DependenciesInfo::new(&self.dependencies, self.game.vanilla_db_table_name_logic()))
128    }
129
130    /// Rebuilds the dependencies of the selected game.
131    ///
132    /// # Arguments
133    ///
134    /// * `only_parent_packs` - If only the parent packs are reloaded, instead of the whole dependencies.
135    /// * `settings` - Settings, to find the game's install folder.
136    ///
137    /// # Returns
138    ///
139    /// The info of the rebuilt dependencies.
140    pub fn rebuild_dependencies(&mut self, only_parent_packs: bool, settings: &Settings) -> Result<DependenciesInfo> {
141        loaded_schema(&self.schema)?;
142
143        let cache_path = dependencies_cache_path()?.join(self.game.dependencies_cache_file_name());
144        let cache_path = if only_parent_packs { None } else { Some(&*cache_path) };
145        let _ = self.load_dependencies(cache_path, true, settings);
146
147        Ok(DependenciesInfo::new(&self.dependencies, self.game.vanilla_db_table_name_logic()))
148    }
149
150    /// Rebuilds the dependencies after the schema changed, if the vanilla data is loaded.
151    ///
152    /// # Arguments
153    ///
154    /// * `settings` - Settings, to find the game's install folder.
155    pub fn rebuild_dependencies_after_schema_update(&mut self, settings: &Settings) -> Result<()> {
156        if !self.dependencies.is_vanilla_data_loaded(false) {
157            return Ok(());
158        }
159
160        let cache_path = dependencies_cache_path()?.join(self.game.dependencies_cache_file_name());
161        self.load_dependencies(Some(&cache_path), true, settings)
162            .map_err(|_| anyhow!("Schema updated, but dependencies cache rebuilding failed. You may need to regenerate it."))
163    }
164
165    /// Rebuilds the dependencies of the selected game, keeping the error for the session status if it fails.
166    ///
167    /// # Arguments
168    ///
169    /// * `cache_path` - Path of the dependencies cache to load, or `None` to only reload the loose and parent files.
170    /// * `decode_tables` - If the tables are decoded with the loaded schema.
171    /// * `settings` - Settings, to find the game's install folder.
172    ///
173    /// # Errors
174    ///
175    /// If any part of the dependencies couldn't be loaded. The parts that could are kept loaded.
176    fn load_dependencies(&mut self, cache_path: Option<&Path>, decode_tables: bool, settings: &Settings) -> Result<()> {
177        let game_path = settings.path_buf(self.game.key());
178        let schema = if decode_tables { &self.schema } else { &None };
179        let result = self.dependencies.rebuild(schema, &parent_pack_names(&self.packs), cache_path, &self.game, &game_path, &settings.path_buf(SECONDARY_PATH));
180
181        // Reloading without the cache doesn't fix a previous cache error, so only a full reload clears it.
182        match &result {
183            Err(_) if !game_path.is_dir() => self.dependencies_error = Some("The install folder of the game is not configured, or doesn't exist.".to_owned()),
184            Err(error) => self.dependencies_error = Some(error.to_string()),
185            Ok(()) if cache_path.is_some() => self.dependencies_error = None,
186            Ok(()) => {},
187        }
188
189        result.map_err(From::from)
190    }
191
192    /// Returns the files of the dependencies of the selected game.
193    pub fn dependencies_info(&self) -> DependenciesInfo {
194        DependenciesInfo::new(&self.dependencies, self.game.vanilla_db_table_name_logic())
195    }
196}