rpkg_rs/resource/
resource_partition.rs

1use crate::resource::partition_manager::PartitionState;
2use crate::resource::pdefs::PartitionInfo;
3use crate::resource::resource_info::ResourceInfo;
4use crate::{utils, GlacierResource, GlacierResourceError, WoaVersion};
5use lazy_regex::regex::Regex;
6use std::cmp::Ordering;
7use std::fmt::Debug;
8use std::{collections::HashMap, path::Path};
9use std::{fmt, io};
10use thiserror::Error;
11
12use crate::resource::resource_package::{ResourcePackage, ResourcePackageError};
13
14use super::runtime_resource_id::RuntimeResourceID;
15
16#[derive(Debug, Error)]
17pub enum ResourcePartitionError {
18    #[error("Failed to open file: {0}")]
19    IoError(#[from] io::Error),
20
21    #[error("Error while reading ResourcePackage({1}): {0}")]
22    ReadResourcePackageError(ResourcePackageError, String),
23
24    #[error("Failed to parse patch index as u16: {0}")]
25    ParsePatchIndexError(#[from] std::num::ParseIntError),
26
27    #[error("Base package not found: {0}")]
28    BasePackageNotFound(String),
29
30    #[error("Failed to read package: {0}")]
31    ReadPackageError(String),
32
33    #[error("No partition mounted")]
34    NotMounted,
35
36    #[error("Resource not available")]
37    ResourceNotAvailable,
38
39    #[error("Interal resource error: {0}")]
40    ResourceError(#[from] GlacierResourceError),
41}
42
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub enum PatchId {
45    Base,
46    Patch(usize),
47}
48
49impl PatchId {
50    pub fn is_base(&self) -> bool {
51        match self {
52            PatchId::Base => true,
53            PatchId::Patch(_) => false,
54        }
55    }
56
57    pub fn is_patch(&self) -> bool {
58        !self.is_base()
59    }
60}
61
62impl Ord for PatchId {
63    fn cmp(&self, other: &Self) -> Ordering {
64        match (self, other) {
65            (PatchId::Base, PatchId::Base) => Ordering::Equal,
66            (PatchId::Base, PatchId::Patch(_)) => Ordering::Less,
67            (PatchId::Patch(_), PatchId::Base) => Ordering::Greater,
68            (PatchId::Patch(a), PatchId::Patch(b)) => a.cmp(b),
69        }
70    }
71}
72
73impl PartialOrd for PatchId {
74    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
75        Some(self.cmp(other))
76    }
77}
78
79pub struct ResourcePartition {
80    info: PartitionInfo,
81    pub packages: HashMap<PatchId, ResourcePackage>,
82    pub(crate) resources: HashMap<RuntimeResourceID, PatchId>,
83}
84
85impl ResourcePartition {
86    pub fn new(info: PartitionInfo) -> Self {
87        Self {
88            info,
89            packages: Default::default(),
90            resources: Default::default(),
91        }
92    }
93
94    /// search through the package_dir to figure out which patch indices are there.
95    /// We have to use this instead of using the patchlevel inside the PartitionInfo.
96    fn read_patch_indices(
97        &self,
98        package_dir: &Path,
99    ) -> Result<Vec<PatchId>, ResourcePartitionError> {
100        let mut patch_indices = vec![];
101
102        let filename = self.info.filename(PatchId::Base);
103        if !package_dir.join(&filename).exists() {
104            return Err(ResourcePartitionError::BasePackageNotFound(filename));
105        }
106
107        let regex_str = format!(r"^(?:{}patch([0-9]+).rpkg)$", self.info.id);
108        let patch_package_re = Regex::new(regex_str.as_str()).unwrap();
109
110        for file_name in utils::read_file_names(package_dir)
111            .iter()
112            .flat_map(|file_name| file_name.to_str())
113        {
114            if let Some(cap) = patch_package_re.captures(file_name) {
115                let patch_level = cap[1].parse::<usize>()?;
116                if patch_level <= self.info.patch_level {
117                    patch_indices.push(PatchId::Patch(patch_level));
118                }
119            }
120        }
121
122        patch_indices.sort();
123        Ok(patch_indices)
124    }
125
126    /// Mounts resource packages in the partition.
127    ///
128    /// This function attempts to mount all necessary resource packages into the current partition.
129    /// If successful, the resources will be available for use within the partition.
130    /// This function will fail silently when this package can't be found inside runtime directory
131    pub fn mount_resource_packages_in_partition(
132        &mut self,
133        runtime_path: &Path,
134    ) -> Result<(), ResourcePartitionError> {
135        self.mount_resource_packages_in_partition_with_callback(runtime_path, |_| {})
136    }
137
138    /// Mounts resource packages in the partition with a callback.
139    ///
140    /// This function attempts to mount all necessary resource packages into the current partition.
141    /// If successful, the resources will be available for use within the partition.
142    /// This function will fail silently when this package can't be found inside runtime directory.
143    pub fn mount_resource_packages_in_partition_with_callback<F>(
144        &mut self,
145        runtime_path: &Path,
146        mut progress_callback: F,
147    ) -> Result<(), ResourcePartitionError>
148    where
149        F: FnMut(&PartitionState),
150    {
151        let mut state = PartitionState {
152            installing: true,
153            mounted: false,
154            install_progress: 0.0,
155        };
156
157        //The process can silently fail here. You are able to detect this using a callback.
158        //This behaviour was chosen because the game is able to refer to non-installed partitions in its packagedefs file.
159        let patch_idx_result = self.read_patch_indices(runtime_path);
160        if patch_idx_result.is_err() {
161            state.installing = false;
162            progress_callback(&state);
163            return Ok(());
164        }
165
166        let patch_indices = patch_idx_result?;
167
168        let base_package_path = runtime_path.join(self.info.filename(PatchId::Base));
169        self.mount_package(base_package_path.as_path(), PatchId::Base)?;
170
171        for (index, patch_id) in patch_indices.clone().into_iter().enumerate() {
172            let patch_package_path = runtime_path.join(self.info.filename(patch_id));
173            self.mount_package(patch_package_path.as_path(), patch_id)?;
174
175            state.install_progress = index as f32 / patch_indices.len() as f32;
176            progress_callback(&state);
177        }
178        state.install_progress = 1.0;
179        state.installing = false;
180        state.mounted = true;
181        progress_callback(&state);
182
183        Ok(())
184    }
185
186    fn mount_package(
187        &mut self,
188        package_path: &Path,
189        patch_index: PatchId,
190    ) -> Result<(), ResourcePartitionError> {
191        let rpkg = ResourcePackage::from_file(package_path).map_err(|e| {
192            ResourcePartitionError::ReadResourcePackageError(
193                e,
194                package_path
195                    .file_name()
196                    .unwrap_or_default()
197                    .to_string_lossy()
198                    .into_owned(),
199            )
200        })?;
201
202        //remove the deletions if there are any
203        for deletion in rpkg.unneeded_resource_ids() {
204            if self.resources.contains_key(deletion) {
205                self.resources.remove_entry(deletion);
206            }
207        }
208
209        for rrid in rpkg.resources.keys() {
210            self.resources.insert(*rrid, patch_index);
211        }
212
213        self.packages.insert(patch_index, rpkg);
214        Ok(())
215    }
216
217    pub fn contains(&self, rrid: &RuntimeResourceID) -> bool {
218        self.resources.contains_key(rrid)
219    }
220
221    pub fn num_patches(&self) -> usize {
222        self.packages.len().saturating_sub(1)
223    }
224
225    /// Returns the latest valid version of all resources.
226    ///
227    /// This function iterates through the resources in the partition
228    /// Will only contain the latest version of a resource and will ignore resources if they are removed by a package.
229    pub fn latest_resources(&self) -> Vec<(&ResourceInfo, PatchId)> {
230        self.resources
231            .iter()
232            .flat_map(|(rrid, idx)| {
233                if let Ok(info) = self.resource_info_from(rrid, *idx) {
234                    Some((info, *idx))
235                } else {
236                    None
237                }
238            })
239            .collect()
240    }
241
242    pub fn latest_resources_of_type(&self, resource_type: &str) -> Vec<(&ResourceInfo, PatchId)>{
243        self.resources
244            .iter()
245            .flat_map(|(rrid, idx)| {
246                if let Ok(info) = self.resource_info_from(rrid, *idx) {
247                    Some((info, *idx))
248                } else {
249                    None
250                }
251            }).filter(|(resource, _)| resource.data_type() == resource_type)
252            .collect()
253    }
254    
255    pub fn latest_resources_of_glacier_type<G: GlacierResource>(&self) -> Vec<(&ResourceInfo, PatchId)>{
256        let resource_type: String = String::from_utf8_lossy(&G::resource_type()).into_owned();
257        self.latest_resources_of_type(resource_type.as_str())
258    }
259
260    /// Returns a list of resources that have been removed.
261    ///
262    /// This function goes through the partition and returns a list of resource marked as unneeded (i.e., deleted). 
263    /// Only resources actually deleted resources will be returned, if a resource is removed and the added again it will be ignored.
264    /// If a package deletes a resource that was never present in any previous package, it will not be included in the returned list
265    pub fn removed_resources(&self) -> Vec<(&ResourceInfo, PatchId)> {
266
267        self.packages
268            .iter()
269            .flat_map(|(patch_id, package)| {
270                package
271                    .unneeded_resource_ids()
272                    .iter()
273                    .map(|&deletion| (*patch_id, *deletion)).collect::<Vec<_>>()
274            }).filter_map(|(_, deletion_rid)| {
275                let patches = self.resource_patch_indices(&deletion_rid);
276                if patches.is_empty() { return None } //resource is not present in any patch
277                if self.resources.contains_key(&deletion_rid) { return None } //resource was deleted, but was added again
278                patches.iter().max().and_then(|&latest_patch| {
279                    self.resource_info_from(&deletion_rid, latest_patch)
280                        .ok().map(|resource_info| (resource_info, latest_patch))
281                })
282            }).collect()
283    }
284
285    pub fn removed_resources_of_type(&self, resource_type: &str) -> Vec<(&ResourceInfo, PatchId)> {
286        self.removed_resources().iter().filter(|(res_info, _)| res_info.data_type() == resource_type).cloned().collect()
287    }
288
289    pub fn removed_resources_of_glacier_type<G: GlacierResource>(&self) -> Vec<(&ResourceInfo, PatchId)>{
290        let resource_type: String = String::from_utf8_lossy(&G::resource_type()).into_owned();
291        self.removed_resources_of_type(resource_type.as_str())
292    }
293    
294    pub fn read_resource(
295        &self,
296        rrid: &RuntimeResourceID,
297    ) -> Result<Vec<u8>, ResourcePartitionError> {
298        let package_index = *self
299            .resources
300            .get(rrid)
301            .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
302
303        let rpkg = self
304            .packages
305            .get(&package_index)
306            .ok_or(ResourcePartitionError::NotMounted)?;
307
308        rpkg.read_resource(rrid).map_err(|e| {
309            ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(package_index))
310        })
311    }
312
313    pub fn read_glacier_resource<T>(
314        &self,
315        woa_version: WoaVersion,
316        rrid: &RuntimeResourceID,
317    ) -> Result<T::Output, ResourcePartitionError>
318    where
319        T: GlacierResource,
320    {
321        let package_index = *self
322            .resources
323            .get(rrid)
324            .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
325
326        let rpkg = self
327            .packages
328            .get(&package_index)
329            .ok_or(ResourcePartitionError::NotMounted)?;
330
331        let bytes = rpkg.read_resource(rrid).map_err(|e| {
332            ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(package_index))
333        })?;
334
335        T::process_data(woa_version, bytes).map_err(ResourcePartitionError::ResourceError)
336    }
337    
338    pub fn read_resource_from(
339        &self,
340        rrid: &RuntimeResourceID,
341        patch_id: PatchId,
342    ) -> Result<Vec<u8>, ResourcePartitionError> {
343        let rpkg = self
344            .packages
345            .get(&patch_id)
346            .ok_or(ResourcePartitionError::NotMounted)?;
347
348        rpkg.read_resource(rrid).map_err(|e| {
349            ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(patch_id))
350        })
351    }
352
353    pub fn get_resource_info(
354        &self,
355        rrid: &RuntimeResourceID,
356    ) -> Result<&ResourceInfo, ResourcePartitionError> {
357        let package_index = self
358            .resources
359            .get(rrid)
360            .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
361
362        let rpkg = self
363            .packages
364            .get(package_index)
365            .ok_or(ResourcePartitionError::NotMounted)?;
366
367        rpkg.resources
368            .get(rrid)
369            .ok_or(ResourcePartitionError::ResourceNotAvailable)
370    }
371
372    pub fn resource_info_from(
373        &self,
374        rrid: &RuntimeResourceID,
375        patch_id: PatchId,
376    ) -> Result<&ResourceInfo, ResourcePartitionError> {
377        let rpkg = self
378            .packages
379            .get(&patch_id)
380            .ok_or(ResourcePartitionError::NotMounted)?;
381
382        rpkg.resources
383            .get(rrid)
384            .ok_or(ResourcePartitionError::ResourceNotAvailable)
385    }
386
387    pub fn partition_info(&self) -> &PartitionInfo {
388        &self.info
389    }
390
391    pub fn resource_patch_indices(&self, rrid: &RuntimeResourceID) -> Vec<PatchId> {
392        self.packages
393            .iter()
394            .filter(|(_, package)| package.resources.contains_key(rrid))
395            .map(|(id, _)| *id)
396            .collect::<Vec<PatchId>>()
397    }
398
399    pub fn resource_removal_indices(&self, rrid: &RuntimeResourceID) -> Vec<PatchId> {
400        self.packages
401            .iter()
402            .filter(|(_, package)| package.has_unneeded_resource(rrid))
403            .map(|(id, _)| *id)
404            .collect::<Vec<PatchId>>()
405    }
406}
407
408impl Debug for ResourcePartition {
409    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
410        let total = self
411            .packages
412            .values()
413            .map(|v| v.resources.len())
414            .sum::<usize>();
415
416        write!(
417            f,
418            "{{index: {}, name: {}, edge_resources: {}, total_resources: {} }}",
419            self.info.filename(PatchId::Base),
420            self.info.name.clone().unwrap_or_default(),
421            self.resources.len(),
422            total
423        )?;
424
425        Ok(())
426    }
427}