Skip to main content

rpfm_ipc/
helpers.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 I&smael 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//! # IPC Helpers Module
12//!
13//! This module provides helper data structures for marshalling complex data between the UI and server.
14//! These types serve as lightweight, serializable representations of core library types, designed
15//! specifically for IPC transfer.
16//!
17//! ## Key Types
18//!
19//! ### Container Information
20//!
21//! - [`ContainerInfo`]: A reduced representation of a PackFile, containing only the metadata needed
22//!   by the UI (file name, path, version, type, compression settings).
23//!
24//! ### File Information
25//!
26//! - [`RFileInfo`]: Metadata about a packed file within a container (path, container name, timestamp,
27//!   file type). Used extensively in tree views and file listings.
28//! - [`VideoInfo`]: Metadata specific to video files (format, dimensions, framerate, etc.).
29//!
30//! ### Dependencies
31//!
32//! - [`DependenciesInfo`]: Contains paths to all dependency files (AssKit tables, vanilla files,
33//!   parent mod files) for populating dependency tree views.
34//!
35//! ### Data Sources
36//!
37//! - [`DataSource`]: Discriminates where data comes from (current PackFile, game files, parent mods,
38//!   AssKit files, or external files). Used throughout the UI to track file origins.
39//!
40//! ### File Creation
41//!
42//! - [`NewFile`]: Enum containing the parameters needed to create new files of various types
43//!   (AnimPack, DB table, Loc file, etc.).
44//!
45//! ### API Responses
46//!
47//! - [`APIResponse`]: Represents update check results (new update available, no update, etc.).
48//!
49//! ## Design Notes
50//!
51//! These types are intentionally simple and focus on data transfer rather than business logic.
52//! They use `#[derive(Serialize, Deserialize)]` for JSON serialization over WebSocket, and
53//! `#[derive(Getters)]` for convenient read-only access to fields.
54
55use getset::Getters;
56use rayon::prelude::*;
57use schemars::JsonSchema;
58use serde_derive::{Serialize, Deserialize};
59
60use std::fmt::{self, Display};
61
62use rpfm_extensions::dependencies::{ASSEMBLY_KIT_TABLE_FILE_NAME, Dependencies};
63
64use crate::api::files::{EntryCopy, FileSource, NewFileKind};
65use crate::api::packs::PackDetails;
66
67use rpfm_lib::compression::CompressionFormat;
68use rpfm_lib::games::{pfh_file_type::PFHFileType, pfh_version::PFHVersion};
69use rpfm_lib::files::{animpack::*, Container, ContainerPath, db::*, FileType, pack::*, RFile, text::TextFormat, video::*};
70
71//-------------------------------------------------------------------------------//
72//                              Enums & Structs
73//-------------------------------------------------------------------------------//
74
75/// This struct is a reduced version of the `PackFile` one, used to pass just the needed data to an UI.
76///
77/// Don't create this one manually. Get it `From` the `PackFile` one, and use it as you need it.
78#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
79#[getset(get = "pub")]
80pub struct ContainerInfo {
81
82    /// The name of the PackFile's file, if exists. If not, then this should be empty.
83    file_name: String,
84
85    /// The path of the PackFile on disk, if exists. If not, then this should be empty.
86    file_path: String,
87
88    /// The version of the PackFile.
89    pfh_version: PFHVersion,
90
91    /// The type of the PackFile.
92    pfh_file_type: PFHFileType,
93
94    /// The bitmasks applied to the PackFile.
95    bitmask: PFHFlags,
96
97    /// If the container needs to be compress on save. None for no compression.
98    compress: CompressionFormat,
99
100    /// The timestamp of the last time the PackFile was saved.
101    timestamp: u64,
102}
103
104/// This struct represents the detailed info about the `PackedFile` we can provide to whoever request it.
105#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
106#[getset(get = "pub")]
107pub struct RFileInfo {
108
109    /// This is the path of the `PackedFile`.
110    path: String,
111
112    /// This is the name of the `Container` this file belongs to.
113    container_name: Option<String>,
114
115    /// This is the ***Last Modified*** time.
116    timestamp: Option<u64>,
117
118    file_type: FileType,
119
120    // If the `PackedFile` is compressed or not.
121    //is_compressed: bool,
122
123    // If the `PackedFile` is encrypted or not.
124    //is_encrypted: bool,
125
126    // If the `PackedFile` has been cached or not.
127    //is_cached: bool,
128
129    // The type of the cached `PackedFile`.
130    //cached_type: String,
131}
132
133/// This struct represents the detailed info about the `PackedFile` we can provide to whoever request it.
134#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
135#[getset(get = "pub")]
136pub struct VideoInfo {
137
138    /// Format of the video file
139    format: SupportedFormats,
140
141    /// Version number.
142    version: u16,
143
144    /// Codec FourCC (usually 'VP80').
145    codec_four_cc: String,
146
147    /// Width of the video in pixels.
148    width: u16,
149
150    /// Height of the video in pixels.
151    height: u16,
152
153    /// Number of frames on the video.
154    num_frames: u32,
155
156    /// Framerate of the video.
157    framerate: f32,
158}
159
160/// This struct contains the minimal data needed (mainly paths), to know what we have loaded in out dependencies.
161///
162/// NOTE: As this is intended to be a "Just use it and discard it" struct, we allow public members to make operations
163/// where we can move out of here faster.
164#[derive(Debug, Clone, Default, Getters, Serialize, Deserialize)]
165#[getset(get = "pub")]
166pub struct DependenciesInfo {
167
168    /// Full PackedFile-like paths of each asskit-only table.
169    pub asskit_tables: Vec<RFileInfo>,
170
171    /// Full list of vanilla PackedFile paths.
172    pub vanilla_packed_files: Vec<RFileInfo>,
173
174    /// Full list of parent PackedFile paths.
175    pub parent_packed_files: Vec<RFileInfo>,
176}
177
178/// This enum represents the source of the data in the view.
179#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd, JsonSchema, Serialize, Deserialize)]
180pub enum DataSource {
181
182    /// This means the data is from somewhere in our PackFile.
183    PackFile,
184
185    /// This means the data is from one of the game files.
186    GameFiles,
187
188    /// This means the data comes from a parent PackFile.
189    ParentFiles,
190
191    /// This means the data comes from the AssKit files.
192    AssKitFiles,
193
194    /// This means the data comes from an external file.
195    ExternalFile,
196}
197
198/// This enum contains the data needed to create a new PackedFile.
199#[derive(Clone, Debug, Serialize, Deserialize)]
200pub enum NewFile {
201
202    /// Name of the file.
203    AnimPack(String),
204
205    /// Name of the file, Name of the Table, Version of the Table.
206    DB(String, String, i32),
207
208    /// Name of the Table.
209    Loc(String),
210
211    /// Name of the file, version of the file, and a list of entries that must be cloned from existing values in vanilla files (from, to).
212    PortraitSettings(String, u32, Vec<(String, String)>),
213
214    /// Name of the file and its format.
215    Text(String, TextFormat),
216
217    /// Name of the file.
218    VMD(String),
219
220    /// Name of the file.
221    WSModel(String),
222}
223
224/// This enum controls the possible responses from the server when checking for an update.
225#[derive(Debug, PartialEq, Eq, serde_derive::Serialize, serde_derive::Deserialize)]
226pub enum APIResponse {
227
228    /// This means a beta update was found.
229    NewBetaUpdate(String),
230
231    /// This means a major stable update was found.
232    NewStableUpdate(String),
233
234    /// This means a minor stable update was found.
235    NewUpdateHotfix(String),
236
237    /// This means no update was found.
238    NoUpdate,
239
240    /// This means don't know if there was an update or not, because the version we got was invalid.
241    UnknownVersion,
242}
243
244/// Information about an active session on the server.
245///
246/// This struct provides a snapshot of a session's state, including connection count
247/// and timeout information. It's used by the session management dialog to display
248/// available sessions and allow users to connect to specific ones.
249#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
250#[getset(get = "pub")]
251pub struct SessionInfo {
252
253    /// Unique identifier for the session.
254    session_id: u64,
255
256    /// Number of active connections to this session.
257    connection_count: u32,
258
259    /// Seconds remaining until session timeout (None if session has active connections).
260    timeout_remaining_secs: Option<u64>,
261
262    /// Whether the session has been marked for shutdown.
263    is_shutting_down: bool,
264
265    /// Names of the pack files currently open in this session.
266    pack_names: Vec<String>,
267}
268
269//-------------------------------------------------------------------------------//
270//                             Implementations
271//-------------------------------------------------------------------------------//
272
273impl From<&Pack> for ContainerInfo {
274    fn from(pack: &Pack) -> Self {
275
276        // If we have no disk file for the pack, it's a new one.
277        let file_name = if pack.disk_file_path().is_empty() {
278            "new_file.pack"
279        } else {
280            pack.disk_file_path().split('/').next_back().unwrap_or("unknown.pack")
281        };
282
283        Self {
284            file_name: file_name.to_string(),
285            file_path: pack.disk_file_path().to_string(),
286            pfh_version: *pack.header().pfh_version(),
287            pfh_file_type: *pack.header().pfh_file_type(),
288            bitmask: *pack.header().bitmask(),
289            timestamp: *pack.header().internal_timestamp(),
290            compress: pack.compression_format(),
291        }
292    }
293}
294
295impl From<&PackDetails> for ContainerInfo {
296    fn from(details: &PackDetails) -> Self {
297        let mut bitmask = PFHFlags::empty();
298        bitmask.set(PFHFlags::HAS_ENCRYPTED_INDEX, details.index_encrypted);
299        bitmask.set(PFHFlags::HAS_ENCRYPTED_DATA, details.data_encrypted);
300        bitmask.set(PFHFlags::HAS_INDEX_WITH_TIMESTAMPS, details.index_includes_timestamp);
301        bitmask.set(PFHFlags::HAS_EXTENDED_HEADER, details.extended_header);
302
303        // Packs not saved on disk have no path.
304        Self {
305            file_name: details.summary.name.clone(),
306            file_path: details.summary.path.clone().unwrap_or_default(),
307            pfh_version: details.version,
308            pfh_file_type: details.summary.pack_type,
309            bitmask,
310            compress: details.compression,
311            timestamp: details.timestamp,
312        }
313    }
314}
315
316/// NOTE: DO NOT USE THIS FOR ANIMPACKS WITHIN PACKS.
317///
318/// It sets the path and name wrong in those cases.
319impl From<&AnimPack> for ContainerInfo {
320    fn from(animpack: &AnimPack) -> Self {
321        Self {
322            file_name: animpack.disk_file_path().split('/').next_back().unwrap_or("unknown.animpack").to_string(),
323            file_path: animpack.disk_file_path().to_string(),
324            ..Default::default()
325        }
326    }
327}
328
329/// Creates a [`ContainerInfo`] from an [`RFileInfo`].
330///
331/// This is used when treating an individual file as if it were a container (e.g., for AnimPacks
332/// stored within a Pack). Most fields default to their default values since a single file
333/// doesn't have pack-level metadata.
334impl From<&RFileInfo> for ContainerInfo {
335    fn from(file_info: &RFileInfo) -> Self {
336        Self {
337            file_name: ContainerPath::File(file_info.path().to_owned()).name().unwrap_or("unknown").to_string(),
338            file_path: file_info.path().to_owned(),
339            ..Default::default()
340        }
341    }
342}
343
344/// Creates an [`RFileInfo`] from an [`RFile`].
345///
346/// Extracts the path, container name, timestamp, and file type from the packed file.
347/// This is the primary way to create file info for display in the UI.
348impl From<&RFile> for RFileInfo {
349    fn from(rfile: &RFile) -> Self {
350        //let is_cached = !matches!(rfile.get_ref_decoded(), DecodedPackedFile::Unknown);
351        //let cached_type = if let DecodedPackedFile::Unknown = rfile.get_ref_decoded() { "Not Yet Cached".to_owned() }
352        //else { format!("{:?}", PackedFileType::from(rfile.get_ref_decoded())) };
353        Self {
354            path: rfile.path_in_container_raw().to_owned(),
355            container_name: rfile.container_name().clone(),
356            timestamp: rfile.timestamp(),
357            file_type: rfile.file_type(),
358            //is_compressed: rfile.get_ref_raw().get_compression_state(),
359            //is_encrypted: rfile.get_ref_raw().get_encryption_state(),
360            //is_cached,
361            //cached_type,
362        }
363    }
364}
365
366/// Creates a [`VideoInfo`] from a [`Video`].
367///
368/// Extracts all video metadata (format, codec, dimensions, frame count, framerate) for display
369/// in the UI's video viewer.
370impl From<&Video> for VideoInfo {
371    fn from(video: &Video) -> Self {
372        Self {
373            format: *video.format(),
374            version: *video.version(),
375            codec_four_cc: video.codec_four_cc().to_string(),
376            width: *video.width(),
377            height: *video.height(),
378            num_frames: *video.num_frames(),
379            framerate: *video.framerate(),
380        }
381    }
382}
383
384impl DependenciesInfo {
385    pub fn new(dependencies: &Dependencies) -> Self {
386        let asskit_tables = dependencies.asskit_only_db_tables().values()
387            .map(|table| RFileInfo::from_db(table, ASSEMBLY_KIT_TABLE_FILE_NAME))
388            .collect::<Vec<RFileInfo>>();
389
390        let vanilla_packed_files = dependencies.vanilla_loose_files()
391            .par_iter()
392            .chain(dependencies.vanilla_files().par_iter())
393            .map(|(_, value)| From::from(value))
394            .collect::<Vec<RFileInfo>>();
395
396        let parent_packed_files = dependencies.parent_files()
397            .par_iter()
398            .map(|(_, value)| From::from(value))
399            .collect::<Vec<RFileInfo>>();
400
401        Self {
402            asskit_tables,
403            vanilla_packed_files,
404            parent_packed_files,
405        }
406    }
407}
408
409impl RFileInfo {
410
411    /// Returns the table name for DB files.
412    ///
413    /// For DB files, the path format is `db/<table_name>/<file_name>`, so this extracts
414    /// the second path component. Returns `None` if the file is not a DB file or if
415    /// the path doesn't have the expected structure.
416    pub fn table_name(&self) -> Option<&str> {
417        if self.file_type == FileType::DB {
418            self.path().split('/').collect::<Vec<_>>().get(1).cloned()
419        } else {
420            None
421        }
422    }
423
424    /// Creates an [`RFileInfo`] from a [`DB`] table and its file name.
425    ///
426    /// This is used to create file info for AssKit-only tables that don't have a backing
427    /// [`RFile`]. The path is constructed as `db/<table_name>/<table_file_name>`.
428    pub fn from_db(db: &DB, table_file_name: &str) -> Self {
429        Self {
430            path: format!("db/{}/{}", db.table_name(), table_file_name),
431            container_name: None,
432            timestamp: None,
433            file_type: FileType::DB,
434        }
435    }
436}
437
438impl DataSource {
439
440    /// Returns the file source of the files of this data source.
441    ///
442    /// # Arguments
443    ///
444    /// * `pack_key` - Key of the pack, for files of open packs.
445    ///
446    /// # Returns
447    ///
448    /// The file source, or `None` for external files.
449    pub fn file_source(&self, pack_key: &str) -> Option<FileSource> {
450        match self {
451            Self::PackFile => Some(FileSource::Pack(pack_key.to_owned())),
452            Self::GameFiles => Some(FileSource::GameFiles),
453            Self::ParentFiles => Some(FileSource::ParentFiles),
454            Self::AssKitFiles => Some(FileSource::AssemblyKit),
455            Self::ExternalFile => None,
456        }
457    }
458}
459
460/// Kind of file of a new file. The name of the file is part of its path, so it's dropped.
461impl From<NewFile> for NewFileKind {
462    fn from(new_file: NewFile) -> Self {
463        match new_file {
464            NewFile::AnimPack(_) => Self::AnimPack,
465            NewFile::DB(_, table_name, version) => Self::Db { table_name, version: Some(version) },
466            NewFile::Loc(_) => Self::Loc,
467            NewFile::PortraitSettings(_, version, entries) => Self::PortraitSettings {
468                version,
469                copy_entries: entries.into_iter().map(|(from, to)| EntryCopy { from, to }).collect(),
470            },
471            NewFile::Text(_, format) => Self::Text { format: Some(format) },
472            NewFile::VMD(_) => Self::Vmd,
473            NewFile::WSModel(_) => Self::WsModel,
474        }
475    }
476}
477
478/// Data source of the files of a file source.
479impl From<&FileSource> for DataSource {
480    fn from(source: &FileSource) -> Self {
481        match source {
482            FileSource::Pack(_) => Self::PackFile,
483            FileSource::GameFiles => Self::GameFiles,
484            FileSource::ParentFiles => Self::ParentFiles,
485            FileSource::AssemblyKit => Self::AssKitFiles,
486        }
487    }
488}
489
490/// Displays the [`DataSource`] as a human-readable string.
491///
492/// Used for logging and UI display purposes.
493impl Display for DataSource {
494    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
495        Display::fmt(match self {
496            Self::PackFile => "PackFile",
497            Self::GameFiles => "GameFiles",
498            Self::ParentFiles => "ParentFiles",
499            Self::AssKitFiles => "AssKitFiles",
500            Self::ExternalFile => "ExternalFile",
501        }, f)
502    }
503}
504
505/// Parses a [`DataSource`] from its string representation.
506///
507/// This is the inverse of the [`Display`] implementation. Panics if the string doesn't match
508/// any known data source.
509impl From<&str> for DataSource {
510    fn from(value: &str) -> Self {
511        match value {
512            "PackFile" => Self::PackFile,
513            "GameFiles" => Self::GameFiles,
514            "ParentFiles" => Self::ParentFiles,
515            "AssKitFiles" => Self::AssKitFiles,
516            "ExternalFile" => Self::ExternalFile,
517            _ => unreachable!("from data source {}", value)
518        }
519    }
520}
521
522impl SessionInfo {
523
524    /// Create a new SessionInfo with the given parameters.
525    pub fn new(
526        session_id: u64,
527        connection_count: u32,
528        timeout_remaining_secs: Option<u64>,
529        is_shutting_down: bool,
530        pack_names: Vec<String>,
531    ) -> Self {
532        Self {
533            session_id,
534            connection_count,
535            timeout_remaining_secs,
536            is_shutting_down,
537            pack_names,
538        }
539    }
540}