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::collections::BTreeMap;
61use std::fmt::{self, Display};
62
63use rpfm_extensions::dependencies::Dependencies;
64use rpfm_extensions::search::{GlobalSearch, SearchSource};
65
66use rpfm_lib::compression::CompressionFormat;
67use rpfm_lib::games::{*, pfh_file_type::PFHFileType, pfh_version::PFHVersion};
68use rpfm_lib::files::{animpack::*, Container, ContainerPath, db::*, FileType, pack::*, RFile, text::TextFormat, video::*};
69
70//-------------------------------------------------------------------------------//
71//                              Enums & Structs
72//-------------------------------------------------------------------------------//
73
74/// This struct is a reduced version of the `PackFile` one, used to pass just the needed data to an UI.
75///
76/// Don't create this one manually. Get it `From` the `PackFile` one, and use it as you need it.
77#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
78#[getset(get = "pub")]
79pub struct ContainerInfo {
80
81    /// The name of the PackFile's file, if exists. If not, then this should be empty.
82    file_name: String,
83
84    /// The path of the PackFile on disk, if exists. If not, then this should be empty.
85    file_path: String,
86
87    /// The version of the PackFile.
88    pfh_version: PFHVersion,
89
90    /// The type of the PackFile.
91    pfh_file_type: PFHFileType,
92
93    /// The bitmasks applied to the PackFile.
94    bitmask: PFHFlags,
95
96    /// If the container needs to be compress on save. None for no compression.
97    compress: CompressionFormat,
98
99    /// The timestamp of the last time the PackFile was saved.
100    timestamp: u64,
101}
102
103/// This struct represents the detailed info about the `PackedFile` we can provide to whoever request it.
104#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
105#[getset(get = "pub")]
106pub struct RFileInfo {
107
108    /// This is the path of the `PackedFile`.
109    path: String,
110
111    /// This is the name of the `Container` this file belongs to.
112    container_name: Option<String>,
113
114    /// This is the ***Last Modified*** time.
115    timestamp: Option<u64>,
116
117    file_type: FileType,
118
119    // If the `PackedFile` is compressed or not.
120    //is_compressed: bool,
121
122    // If the `PackedFile` is encrypted or not.
123    //is_encrypted: bool,
124
125    // If the `PackedFile` has been cached or not.
126    //is_cached: bool,
127
128    // The type of the cached `PackedFile`.
129    //cached_type: String,
130}
131
132/// This struct represents the detailed info about the `PackedFile` we can provide to whoever request it.
133#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
134#[getset(get = "pub")]
135pub struct VideoInfo {
136
137    /// Format of the video file
138    format: SupportedFormats,
139
140    /// Version number.
141    version: u16,
142
143    /// Codec FourCC (usually 'VP80').
144    codec_four_cc: String,
145
146    /// Width of the video in pixels.
147    width: u16,
148
149    /// Height of the video in pixels.
150    height: u16,
151
152    /// Number of frames on the video.
153    num_frames: u32,
154
155    /// Framerate of the video.
156    framerate: f32,
157}
158
159/// This struct contains the minimal data needed (mainly paths), to know what we have loaded in out dependencies.
160///
161/// NOTE: As this is intended to be a "Just use it and discard it" struct, we allow public members to make operations
162/// where we can move out of here faster.
163#[derive(Debug, Clone, Default, Getters, Serialize, Deserialize)]
164#[getset(get = "pub")]
165pub struct DependenciesInfo {
166
167    /// Full PackedFile-like paths of each asskit-only table.
168    pub asskit_tables: Vec<RFileInfo>,
169
170    /// Full list of vanilla PackedFile paths.
171    pub vanilla_packed_files: Vec<RFileInfo>,
172
173    /// Full list of parent PackedFile paths.
174    pub parent_packed_files: Vec<RFileInfo>,
175}
176
177/// This enum represents the source of the data in the view.
178#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Ord, PartialOrd, JsonSchema, Serialize, Deserialize)]
179pub enum DataSource {
180
181    /// This means the data is from somewhere in our PackFile.
182    PackFile,
183
184    /// This means the data is from one of the game files.
185    GameFiles,
186
187    /// This means the data comes from a parent PackFile.
188    ParentFiles,
189
190    /// This means the data comes from the AssKit files.
191    AssKitFiles,
192
193    /// This means the data comes from an external file.
194    ExternalFile,
195}
196
197/// This enum contains the data needed to create a new PackedFile.
198#[derive(Clone, Debug, Serialize, Deserialize)]
199pub enum NewFile {
200
201    /// Name of the file.
202    AnimPack(String),
203
204    /// Name of the file, Name of the Table, Version of the Table.
205    DB(String, String, i32),
206
207    /// Name of the Table.
208    Loc(String),
209
210    /// 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).
211    PortraitSettings(String, u32, Vec<(String, String)>),
212
213    /// Name of the file and its format.
214    Text(String, TextFormat),
215
216    /// Name of the file.
217    VMD(String),
218
219    /// Name of the file.
220    WSModel(String),
221}
222
223/// This enum controls the possible responses from the server when checking for an update.
224#[derive(Debug, PartialEq, Eq, serde_derive::Serialize, serde_derive::Deserialize)]
225pub enum APIResponse {
226
227    /// This means a beta update was found.
228    NewBetaUpdate(String),
229
230    /// This means a major stable update was found.
231    NewStableUpdate(String),
232
233    /// This means a minor stable update was found.
234    NewUpdateHotfix(String),
235
236    /// This means no update was found.
237    NoUpdate,
238
239    /// This means don't know if there was an update or not, because the version we got was invalid.
240    UnknownVersion,
241}
242
243/// Information about an active session on the server.
244///
245/// This struct provides a snapshot of a session's state, including connection count
246/// and timeout information. It's used by the session management dialog to display
247/// available sessions and allow users to connect to specific ones.
248#[derive(Clone, Debug, Default, Getters, Serialize, Deserialize)]
249#[getset(get = "pub")]
250pub struct SessionInfo {
251
252    /// Unique identifier for the session.
253    session_id: u64,
254
255    /// Number of active connections to this session.
256    connection_count: u32,
257
258    /// Seconds remaining until session timeout (None if session has active connections).
259    timeout_remaining_secs: Option<u64>,
260
261    /// Whether the session has been marked for shutdown.
262    is_shutting_down: bool,
263
264    /// Names of the pack files currently open in this session.
265    pack_names: Vec<String>,
266}
267
268//-------------------------------------------------------------------------------//
269//                             Implementations
270//-------------------------------------------------------------------------------//
271
272impl From<&Pack> for ContainerInfo {
273    fn from(pack: &Pack) -> Self {
274
275        // If we have no disk file for the pack, it's a new one.
276        let file_name = if pack.disk_file_path().is_empty() {
277            "new_file.pack"
278        } else {
279            pack.disk_file_path().split('/').next_back().unwrap_or("unknown.pack")
280        };
281
282        Self {
283            file_name: file_name.to_string(),
284            file_path: pack.disk_file_path().to_string(),
285            pfh_version: *pack.header().pfh_version(),
286            pfh_file_type: *pack.header().pfh_file_type(),
287            bitmask: *pack.header().bitmask(),
288            timestamp: *pack.header().internal_timestamp(),
289            compress: pack.compression_format(),
290        }
291    }
292}
293
294/// NOTE: DO NOT USE THIS FOR ANIMPACKS WITHIN PACKS.
295///
296/// It sets the path and name wrong in those cases.
297impl From<&AnimPack> for ContainerInfo {
298    fn from(animpack: &AnimPack) -> Self {
299        Self {
300            file_name: animpack.disk_file_path().split('/').next_back().unwrap_or("unknown.animpack").to_string(),
301            file_path: animpack.disk_file_path().to_string(),
302            ..Default::default()
303        }
304    }
305}
306
307/// Creates a [`ContainerInfo`] from an [`RFileInfo`].
308///
309/// This is used when treating an individual file as if it were a container (e.g., for AnimPacks
310/// stored within a Pack). Most fields default to their default values since a single file
311/// doesn't have pack-level metadata.
312impl From<&RFileInfo> for ContainerInfo {
313    fn from(file_info: &RFileInfo) -> Self {
314        Self {
315            file_name: ContainerPath::File(file_info.path().to_owned()).name().unwrap_or("unknown").to_string(),
316            file_path: file_info.path().to_owned(),
317            ..Default::default()
318        }
319    }
320}
321
322/// Creates an [`RFileInfo`] from an [`RFile`].
323///
324/// Extracts the path, container name, timestamp, and file type from the packed file.
325/// This is the primary way to create file info for display in the UI.
326impl From<&RFile> for RFileInfo {
327    fn from(rfile: &RFile) -> Self {
328        //let is_cached = !matches!(rfile.get_ref_decoded(), DecodedPackedFile::Unknown);
329        //let cached_type = if let DecodedPackedFile::Unknown = rfile.get_ref_decoded() { "Not Yet Cached".to_owned() }
330        //else { format!("{:?}", PackedFileType::from(rfile.get_ref_decoded())) };
331        Self {
332            path: rfile.path_in_container_raw().to_owned(),
333            container_name: rfile.container_name().clone(),
334            timestamp: rfile.timestamp(),
335            file_type: rfile.file_type(),
336            //is_compressed: rfile.get_ref_raw().get_compression_state(),
337            //is_encrypted: rfile.get_ref_raw().get_encryption_state(),
338            //is_cached,
339            //cached_type,
340        }
341    }
342}
343
344/// Creates a [`VideoInfo`] from a [`Video`].
345///
346/// Extracts all video metadata (format, codec, dimensions, frame count, framerate) for display
347/// in the UI's video viewer.
348impl From<&Video> for VideoInfo {
349    fn from(video: &Video) -> Self {
350        Self {
351            format: *video.format(),
352            version: *video.version(),
353            codec_four_cc: video.codec_four_cc().to_string(),
354            width: *video.width(),
355            height: *video.height(),
356            num_frames: *video.num_frames(),
357            framerate: *video.framerate(),
358        }
359    }
360}
361
362impl DependenciesInfo {
363    pub fn new(dependencies: &Dependencies, table_name_logic: &VanillaDBTableNameLogic) -> Self {
364        let asskit_tables = dependencies.asskit_only_db_tables().values().map(|table| {
365            let table_name = match table_name_logic {
366                VanillaDBTableNameLogic::DefaultName(ref name) => name,
367                VanillaDBTableNameLogic::FolderName => table.table_name(),
368            };
369
370            RFileInfo::from_db(table, table_name)
371        }).collect::<Vec<RFileInfo>>();
372
373        let vanilla_packed_files = dependencies.vanilla_loose_files()
374            .par_iter()
375            .chain(dependencies.vanilla_files().par_iter())
376            .map(|(_, value)| From::from(value))
377            .collect::<Vec<RFileInfo>>();
378
379        let parent_packed_files = dependencies.parent_files()
380            .par_iter()
381            .map(|(_, value)| From::from(value))
382            .collect::<Vec<RFileInfo>>();
383
384        Self {
385            asskit_tables,
386            vanilla_packed_files,
387            parent_packed_files,
388        }
389    }
390}
391
392impl RFileInfo {
393
394    /// This function returns the PackedFileInfo for all the PackedFiles the current search has searched on.
395    pub fn info_from_global_search(global_search: &GlobalSearch, packs: &BTreeMap<String, Pack>) -> Vec<Self> {
396        let types = global_search.search_on().types_to_search();
397
398        // Only return info of stuff on the local Packs.
399        if global_search.sources().iter().any(|s| matches!(s, SearchSource::Pack(_))) {
400            packs.values().flat_map(|pack| pack.files_by_type(&types).iter().map(|x| From::from(*x)).collect::<Vec<_>>()).collect()
401        } else {
402            vec![]
403        }
404    }
405
406    /// Returns the table name for DB files.
407    ///
408    /// For DB files, the path format is `db/<table_name>/<file_name>`, so this extracts
409    /// the second path component. Returns `None` if the file is not a DB file or if
410    /// the path doesn't have the expected structure.
411    pub fn table_name(&self) -> Option<&str> {
412        if self.file_type == FileType::DB {
413            self.path().split('/').collect::<Vec<_>>().get(1).cloned()
414        } else {
415            None
416        }
417    }
418
419    /// Creates an [`RFileInfo`] from a [`DB`] table and its file name.
420    ///
421    /// This is used to create file info for AssKit-only tables that don't have a backing
422    /// [`RFile`]. The path is constructed as `db/<table_name>/<table_file_name>`.
423    pub fn from_db(db: &DB, table_file_name: &str) -> Self {
424        Self {
425            path: format!("db/{}/{}", db.table_name(), table_file_name),
426            container_name: None,
427            timestamp: None,
428            file_type: FileType::DB,
429        }
430    }
431}
432
433/// Displays the [`DataSource`] as a human-readable string.
434///
435/// Used for logging and UI display purposes.
436impl Display for DataSource {
437    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
438        Display::fmt(match self {
439            Self::PackFile => "PackFile",
440            Self::GameFiles => "GameFiles",
441            Self::ParentFiles => "ParentFiles",
442            Self::AssKitFiles => "AssKitFiles",
443            Self::ExternalFile => "ExternalFile",
444        }, f)
445    }
446}
447
448/// Parses a [`DataSource`] from its string representation.
449///
450/// This is the inverse of the [`Display`] implementation. Panics if the string doesn't match
451/// any known data source.
452impl From<&str> for DataSource {
453    fn from(value: &str) -> Self {
454        match value {
455            "PackFile" => Self::PackFile,
456            "GameFiles" => Self::GameFiles,
457            "ParentFiles" => Self::ParentFiles,
458            "AssKitFiles" => Self::AssKitFiles,
459            "ExternalFile" => Self::ExternalFile,
460            _ => unreachable!("from data source {}", value)
461        }
462    }
463}
464
465impl SessionInfo {
466
467    /// Create a new SessionInfo with the given parameters.
468    pub fn new(
469        session_id: u64,
470        connection_count: u32,
471        timeout_remaining_secs: Option<u64>,
472        is_shutting_down: bool,
473        pack_names: Vec<String>,
474    ) -> Self {
475        Self {
476            session_id,
477            connection_count,
478            timeout_remaining_secs,
479            is_shutting_down,
480            pack_names,
481        }
482    }
483}