rpkg_rs/resource/
partition_manager.rs

1use rayon::iter::ParallelIterator;
2use rayon::iter::IndexedParallelIterator;
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, Mutex};
5use itertools::Itertools;
6use rayon::prelude::IntoParallelRefIterator;
7use thiserror::Error;
8use crate::resource::partition_manager::PartitionManagerError::PartitionNotFound;
9
10use crate::resource::pdefs::{
11    GameDiscoveryError, GamePaths, PackageDefinitionError, PackageDefinitionSource, PartitionId,
12    PartitionInfo,
13};
14use crate::resource::resource_info::ResourceInfo;
15use crate::resource::runtime_resource_id::RuntimeResourceID;
16use crate::WoaVersion;
17
18use super::resource_partition::{PatchId, ResourcePartition, ResourcePartitionError};
19
20#[derive(Debug, Error)]
21pub enum PartitionManagerError {
22    #[error("Cannot use packagedefinition config: {0}")]
23    PackageDefinitionError(#[from] PackageDefinitionError),
24
25    #[error("partition '{0}' error: {1}")]
26    PartitionError(PartitionId, ResourcePartitionError),
27
28    #[error("partition {0} could not be found")]
29    PartitionNotFound(String),
30    
31    #[error("resource {0} could not be found")]
32    ResourceNotFound(String),
33
34    #[error("Could not discover game paths: {0}")]
35    GameDiscoveryError(#[from] GameDiscoveryError),
36    
37    #[error("Could not locate runtime directory: {0}")]
38    RuntimeDirectoryNotFound(PathBuf),
39    
40    #[error("Could not find a root partition")]
41    NoRootPartition(),
42}
43
44#[allow(dead_code)]
45#[derive(Clone, Debug, Copy)]
46pub struct PartitionState {
47    pub installing: bool,
48    pub mounted: bool,
49    pub install_progress: f32,
50}
51
52pub struct PartitionManager {
53    runtime_directory: PathBuf,
54    partition_infos: Vec<PartitionInfo>, //All potential partitions which could be mounted with this manager
55    pub partitions: Vec<ResourcePartition>, //All mounted partitions
56}
57
58#[cfg(feature = "rayon")]
59pub trait PartitionManagerPar {
60    fn from_game_par(
61        retail_directory: PathBuf,
62        game_version: WoaVersion,
63        mount: bool,
64    ) -> Result<Self, PartitionManagerError> where Self: Sized;
65
66    fn from_game_with_callback_par<F>(
67        retail_directory: PathBuf,
68        game_version: WoaVersion,
69        mount: bool,
70        progress_callback: F,
71    ) -> Result<Self, PartitionManagerError>
72    where
73        F: FnMut(usize, &PartitionState) + Send + Sync, Self: Sized;
74
75    fn mount_partitions_par<F>(&mut self, progress_callback: F) -> Result<(), PartitionManagerError>
76    where
77        F: FnMut(usize, &PartitionState) + Send + Sync;
78
79}
80
81impl PartitionManager {
82    /// Create a new PartitionManager for the game at the given path, and a custom package definition.
83    ///
84    /// # Arguments
85    /// - `runtime_directory` - The path to the game's runtime directory.
86    /// - `package_definition` - The package definition to use.
87    pub fn new(
88        runtime_directory: PathBuf,
89        package_definition: &PackageDefinitionSource,
90    ) -> Result<Self, PartitionManagerError> {
91        
92        if !runtime_directory.exists() {
93            return Err(PartitionManagerError::RuntimeDirectoryNotFound(runtime_directory));
94        }
95        
96        let partition_infos = package_definition
97            .read()
98            .map_err(PartitionManagerError::PackageDefinitionError)?;
99
100        Ok(Self {
101            runtime_directory,
102            partition_infos,
103            partitions: vec![],
104        })
105    }
106
107    /// Create a new PartitionManager by mounting the game at the given path.
108    ///
109    /// # Arguments
110    /// - `retail_path` - The path to the game's retail directory.
111    /// - `game_version` - The version of the game.
112    /// - `mount` - Indicates whether to automatically mount the partitions, can eliminate the need to call `mount_partitions` separately
113    pub fn from_game(
114        retail_directory: PathBuf,
115        game_version: WoaVersion,
116        mount: bool,
117    ) -> Result<Self, PartitionManagerError> {
118        Self::from_game_with_callback(retail_directory, game_version, mount, |_, _| {})
119    }
120
121    /// Create a new PartitionManager by mounting the game at the given path.
122    ///
123    /// # Arguments
124    /// - `retail_path` - The path to the game's retail directory.
125    /// - `game_version` - The version of the game.
126    /// - `mount` - Indicates whether to automatically mount the partitions, can eliminate the need to call `mount_partitions` separately
127    /// - `progress_callback` - A callback function that will be called with the current mounting progress.
128    pub fn from_game_with_callback<F>(
129        retail_directory: PathBuf,
130        game_version: WoaVersion,
131        mount: bool,
132        progress_callback: F,
133    ) -> Result<Self, PartitionManagerError>
134    where
135        F: FnMut(usize, &PartitionState),
136    {
137        let game_paths = GamePaths::from_retail_directory(retail_directory)?;
138        let package_definition =
139            PackageDefinitionSource::from_file(game_paths.package_definition_path, game_version)?;
140
141        // And read all the partition infos.
142        let partition_infos = package_definition
143            .read()
144            .map_err(PartitionManagerError::PackageDefinitionError)?;
145
146        let mut package_manager = Self {
147            runtime_directory: game_paths.runtime_path,
148            partition_infos,
149            partitions: vec![],
150        };
151
152        // If the user requested auto mounting, do it.
153        if mount {
154            package_manager.mount_partitions(progress_callback)?;
155        }
156
157        Ok(package_manager)
158    }
159
160    fn try_read_partition<F>(
161        runtime_directory: &Path,
162        partition_info: PartitionInfo,
163        mut progress_callback: F,
164    ) -> Result<Option<ResourcePartition>, PartitionManagerError>
165    where
166        F: FnMut(&PartitionState),
167    {
168        let mut partition = ResourcePartition::new(partition_info.clone());
169        let mut state_result: PartitionState = PartitionState {
170            installing: false,
171            mounted: false,
172            install_progress: 0.0,
173        };
174
175        let callback = |state: &_| {
176            progress_callback(state);
177            state_result = *state;
178        };
179
180        partition
181            .mount_resource_packages_in_partition_with_callback(runtime_directory, callback)
182            .map_err(|e| PartitionManagerError::PartitionError(partition_info.id, e))?;
183
184        if state_result.mounted {
185            Ok(Some(partition))
186        } else {
187            Ok(None)
188        }
189    }
190
191    /// Mount all the partitions in the game.
192    ///
193    /// # Arguments
194    /// - `progress_callback` - A callback function that will be called with the current mounting progress.
195    pub fn mount_partitions<F>(
196        &mut self,
197        mut progress_callback: F,
198    ) -> Result<(), PartitionManagerError>
199    where
200        F: FnMut(usize, &PartitionState),
201    {
202        let partitions = self
203            .partition_infos
204            .iter()
205            .enumerate()
206            .map(|(index, partition_info)| {
207                let callback = |state: &_| {
208                    progress_callback(index + 1, state);
209                };
210
211                Self::try_read_partition(&self.runtime_directory, partition_info.clone(), callback)
212            })
213            .collect::<Result<Vec<Option<ResourcePartition>>, PartitionManagerError>>()?
214            .into_iter()
215            .flatten()
216            .collect::<Vec<ResourcePartition>>();
217
218        for partition in partitions {
219            self.partitions.push(partition);
220        }
221
222        Ok(())
223    }
224
225    /// Mount a single partition in the game.
226    ///
227    /// # Arguments
228    /// - `partition_info` - The partition info to mount.
229    /// - `progress_callback` - A callback function that will be called with the current mounting progress.
230    pub fn mount_partition<F>(
231        &mut self,
232        partition_info: PartitionInfo,
233        progress_callback: F,
234    ) -> Result<(), PartitionManagerError>
235    where
236        F: FnMut(&PartitionState),
237    {
238        if let Some(partition) =
239            Self::try_read_partition(&self.runtime_directory, partition_info, progress_callback)?
240        {
241            self.partitions.push(partition)
242        }
243
244        Ok(())
245    }
246
247    pub fn read_resource_from(
248        &self,
249        partition_id: PartitionId,
250        rrid: RuntimeResourceID,
251    ) -> Result<Vec<u8>, PartitionManagerError> {
252        let partition = self
253            .partitions
254            .iter()
255            .find(|partition| partition.partition_info().id == partition_id);
256
257        if let Some(partition) = partition {
258            match partition.read_resource(&rrid) {
259                Ok(data) => Ok(data),
260                Err(e) => Err(PartitionManagerError::PartitionError(partition_id, e)),
261            }
262        } else {
263            Err(PartitionManagerError::PartitionNotFound(
264                partition_id.to_string(),
265            ))
266        }
267    }
268
269    pub fn find_partition(&self, partition_id: PartitionId) -> Option<&ResourcePartition> {
270        self.partitions
271            .iter()
272            .find(|partition| partition.partition_info().id == partition_id)
273    }
274    
275    pub fn root_partition(
276        &self
277    ) -> Result<PartitionId, PartitionManagerError> {
278        if let Some(mut partition) = self.partition_infos.first(){
279            loop {
280                match &partition.parent{
281                    Some(parent) => {
282                        match self.find_partition(parent.clone()){
283                            Some(part) => {partition = part.partition_info()}
284                            None => {return Err(PartitionNotFound(parent.to_string()))}
285                        };
286                    },
287                    None => return Ok(partition.id.clone()),
288                }
289            }
290        }
291        Err(PartitionManagerError::NoRootPartition())
292    }
293    
294    pub fn partitions_with_resource(&self, rrid: &RuntimeResourceID) -> Vec<PartitionId> {
295        self.partitions
296            .iter()
297            .filter_map(|partition| {
298                if partition.contains(rrid) {
299                    Some(partition.partition_info().id.clone())
300                } else {
301                    None
302                }
303            })
304            .collect()
305    }
306
307    /// Iterates over all `RuntimeResourceID`s across all partitions.
308    ///
309    /// # Returns
310    /// An iterator yielding references to `RuntimeResourceID`.
311    pub fn iter_all_runtime_resource_ids(&self) -> impl Iterator<Item = &RuntimeResourceID> + '_ {
312        self.partitions.iter().flat_map(|partition| {
313            partition.resources.keys()
314        })
315    }
316    
317    pub fn resource_mounted(&self, rrid: &RuntimeResourceID) -> bool{
318        self.iter_all_runtime_resource_ids().contains(rrid)
319    }
320    
321    pub fn resource_infos(&self, rrid: &RuntimeResourceID) -> Vec<(PartitionId, &ResourceInfo)> {
322        self.partitions_with_resource(rrid)
323            .into_iter()
324            .filter_map(|p_id| {
325                if let Ok(info) = self.resource_info_from(&p_id, rrid) {
326                    Some((p_id, info))
327                } else {
328                    None
329                }
330            })
331            .collect()
332    }
333
334    pub fn resource_info_from(
335        &self,
336        partition_id: &PartitionId,
337        rrid: &RuntimeResourceID,
338    ) -> Result<&ResourceInfo, PartitionManagerError> {
339        let partition = self
340            .partitions
341            .iter()
342            .find(|partition| partition.partition_info().id == *partition_id);
343
344        if let Some(partition) = partition {
345            match partition.get_resource_info(rrid) {
346                Ok(info) => Ok(info),
347                Err(e) => Err(PartitionManagerError::PartitionError(partition_id.clone(), e)),
348            }
349        } else {
350            Err(PartitionManagerError::PartitionNotFound(
351                partition_id.to_string(),
352            ))
353        }
354    }
355
356    /// Resolves a resource by traversing the partition hierarchy.
357    ///
358    /// # Arguments
359    /// - `partition_id` - The ID of the partition to start resolving from.
360    /// - `resource_id` - The ID of the resource to resolve.
361    pub fn resolve_resource_from(
362        &self,
363        partition_id: PartitionId,
364        resource_id: &RuntimeResourceID,
365    ) -> Result<(&ResourceInfo, PartitionId), PartitionManagerError> {
366        match self.find_partition(partition_id.clone()) {
367            Some(partition) => {
368                if partition.contains(resource_id) {
369                    match partition.get_resource_info(resource_id) {
370                        Ok(info) => Ok((info, partition_id.clone())),
371                        Err(_) => Err(PartitionManagerError::ResourceNotFound(resource_id.to_string())),
372                    }
373                } else {
374                    match &partition.partition_info().parent {
375                        
376                        Some(parent_id) => {
377                            self.resolve_resource_from(parent_id.clone(), resource_id)
378                        },
379                        None => {
380                            Err(PartitionManagerError::ResourceNotFound(resource_id.to_string()))
381                        }
382                    }
383                }
384            },
385            None => {
386                Err(PartitionNotFound(partition_id.to_string()))
387            }
388        }
389    }
390    #[deprecated(
391        since = "1.0.0",
392        note = "prefer direct access through the partitions field"
393    )]
394    pub fn partitions(&self) -> &Vec<ResourcePartition> {
395        &self.partitions
396    }
397
398    #[deprecated(
399        since = "1.1.0",
400        note = "please implement this yourself, it is out of scope for this struct"
401    )]
402    pub fn print_resource_changelog(&self, rrid: &RuntimeResourceID) {
403        println!("Resource: {rrid}");
404
405        for partition in &self.partitions {
406            let mut last_occurence: Option<&ResourceInfo> = None;
407
408            let size =
409                |info: &ResourceInfo| info.compressed_size().unwrap_or(info.header.data_size);
410
411            let changes = partition.resource_patch_indices(rrid);
412            let deletions = partition.resource_removal_indices(rrid);
413            let occurrences = changes
414                .clone()
415                .into_iter()
416                .chain(deletions.clone().into_iter())
417                .collect::<Vec<PatchId>>();
418
419            for occurence in occurrences.iter().sorted() {
420                println!(
421                    "{}: {}",
422                    match occurence {
423                        PatchId::Base => {
424                            "Base"
425                        }
426                        PatchId::Patch(_) => {
427                            "Patch"
428                        }
429                    },
430                    partition.partition_info().filename(*occurence)
431                );
432
433                if deletions.contains(occurence) {
434                    println!("\t- Removal: resource deleted");
435                    last_occurence = None;
436                }
437
438                if changes.contains(occurence) {
439                    if let Ok(info) = partition.resource_info_from(rrid, *occurence) {
440                        if let Some(last_info) = last_occurence {
441                            println!(
442                                "\t- Modification: Size changed from {} to {}",
443                                size(last_info),
444                                size(info)
445                            );
446                        } else {
447                            println!("\t- Addition: New occurrence, Size {} bytes", size(info))
448                        }
449                        last_occurence = Some(info);
450                    }
451                }
452            }
453        }
454    }
455}
456
457#[cfg(feature = "rayon")]
458impl PartitionManagerPar for PartitionManager {
459    /// Create a new PartitionManager by mounting the game at the given path, but parallel.
460    ///
461    /// # Arguments
462    /// - `retail_path` - The path to the game's retail directory.
463    /// - `game_version` - The version of the game.
464    /// - `mount` - Indicates whether to automatically mount the partitions, can eliminate the need to call `mount_partitions_par` separately
465    fn from_game_par(
466        retail_directory: PathBuf,
467        game_version: WoaVersion,
468        mount: bool,
469    ) -> Result<Self, PartitionManagerError> {
470        Self::from_game_with_callback_par(retail_directory, game_version, mount, |_, _| {})
471    }
472
473    /// Create a new PartitionManager by mounting the game at the given path, but parallel.
474    ///
475    /// # Arguments
476    /// - `retail_path` - The path to the game's retail directory.
477    /// - `game_version` - The version of the game.
478    /// - `mount` - Indicates whether to automatically mount the partitions, can eliminate the need to call `mount_partitions_par` separately
479    /// - `progress_callback` - A callback function that will be called with the current mounting progress.
480    fn from_game_with_callback_par<F>(
481        retail_directory: PathBuf,
482        game_version: WoaVersion,
483        mount: bool,
484        progress_callback: F,
485    ) -> Result<Self, PartitionManagerError>
486    where
487        F: FnMut(usize, &PartitionState) + Send + Sync,
488    {
489        let game_paths = GamePaths::from_retail_directory(retail_directory)?;
490        let package_definition =
491            PackageDefinitionSource::from_file(game_paths.package_definition_path, game_version)?;
492
493        // And read all the partition infos.
494        let partition_infos = package_definition
495            .read()
496            .map_err(PartitionManagerError::PackageDefinitionError)?;
497
498        let mut package_manager = Self {
499            runtime_directory: game_paths.runtime_path,
500            partition_infos,
501            partitions: vec![],
502        };
503
504        // If the user requested auto mounting, do it.
505        if mount {
506            package_manager.mount_partitions_par(progress_callback)?;
507        }
508
509        Ok(package_manager)
510    }
511
512    /// Mount all the partitions in the game, parallelly.
513    ///
514    /// # Arguments
515    /// - `progress_callback` - A callback function that will be called with the current mounting progress.
516    fn mount_partitions_par<F>(&mut self, progress_callback: F) -> Result<(), PartitionManagerError>
517    where
518        F: FnMut(usize, &PartitionState) + Send + Sync,
519    {
520        let progress_callback = Arc::new(Mutex::new(progress_callback));
521
522        let runtime_directory = self.runtime_directory.clone(); // Clone if needed
523
524        let results: Result<Vec<_>, PartitionManagerError> = self.partition_infos
525            .par_iter()
526            .enumerate()
527            .map(|(index, partition_info)| {
528                Self::try_read_partition(&runtime_directory, partition_info.clone(), |state| {
529                    let mut cb = progress_callback.lock().unwrap();
530                    cb(index, state)
531                })
532            })
533            .filter_map(|result| match result {
534                Ok(Some(partition)) => Some(Ok(partition)),
535                Ok(None) => None,
536                Err(e) => Some(Err(e)),
537            })
538            .collect();
539
540        for partition in results? {
541            self.partitions.push(partition);
542        }
543
544        Ok(())
545    }
546}