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>, pub partitions: Vec<ResourcePartition>, }
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 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 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 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 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 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 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 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 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 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 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 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 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 mount {
506 package_manager.mount_partitions_par(progress_callback)?;
507 }
508
509 Ok(package_manager)
510 }
511
512 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(); 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}