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::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, table_name_logic: &VanillaDBTableNameLogic) -> Self {
386 let asskit_tables = dependencies.asskit_only_db_tables().values().map(|table| {
387 let table_name = match table_name_logic {
388 VanillaDBTableNameLogic::DefaultName(ref name) => name,
389 VanillaDBTableNameLogic::FolderName => table.table_name(),
390 };
391
392 RFileInfo::from_db(table, table_name)
393 }).collect::<Vec<RFileInfo>>();
394
395 let vanilla_packed_files = dependencies.vanilla_loose_files()
396 .par_iter()
397 .chain(dependencies.vanilla_files().par_iter())
398 .map(|(_, value)| From::from(value))
399 .collect::<Vec<RFileInfo>>();
400
401 let parent_packed_files = dependencies.parent_files()
402 .par_iter()
403 .map(|(_, value)| From::from(value))
404 .collect::<Vec<RFileInfo>>();
405
406 Self {
407 asskit_tables,
408 vanilla_packed_files,
409 parent_packed_files,
410 }
411 }
412}
413
414impl RFileInfo {
415
416 /// Returns the table name for DB files.
417 ///
418 /// For DB files, the path format is `db/<table_name>/<file_name>`, so this extracts
419 /// the second path component. Returns `None` if the file is not a DB file or if
420 /// the path doesn't have the expected structure.
421 pub fn table_name(&self) -> Option<&str> {
422 if self.file_type == FileType::DB {
423 self.path().split('/').collect::<Vec<_>>().get(1).cloned()
424 } else {
425 None
426 }
427 }
428
429 /// Creates an [`RFileInfo`] from a [`DB`] table and its file name.
430 ///
431 /// This is used to create file info for AssKit-only tables that don't have a backing
432 /// [`RFile`]. The path is constructed as `db/<table_name>/<table_file_name>`.
433 pub fn from_db(db: &DB, table_file_name: &str) -> Self {
434 Self {
435 path: format!("db/{}/{}", db.table_name(), table_file_name),
436 container_name: None,
437 timestamp: None,
438 file_type: FileType::DB,
439 }
440 }
441}
442
443impl DataSource {
444
445 /// Returns the file source of the files of this data source.
446 ///
447 /// # Arguments
448 ///
449 /// * `pack_key` - Key of the pack, for files of open packs.
450 ///
451 /// # Returns
452 ///
453 /// The file source, or `None` for external files.
454 pub fn file_source(&self, pack_key: &str) -> Option<FileSource> {
455 match self {
456 Self::PackFile => Some(FileSource::Pack(pack_key.to_owned())),
457 Self::GameFiles => Some(FileSource::GameFiles),
458 Self::ParentFiles => Some(FileSource::ParentFiles),
459 Self::AssKitFiles => Some(FileSource::AssemblyKit),
460 Self::ExternalFile => None,
461 }
462 }
463}
464
465/// Kind of file of a new file. The name of the file is part of its path, so it's dropped.
466impl From<NewFile> for NewFileKind {
467 fn from(new_file: NewFile) -> Self {
468 match new_file {
469 NewFile::AnimPack(_) => Self::AnimPack,
470 NewFile::DB(_, table_name, version) => Self::Db { table_name, version: Some(version) },
471 NewFile::Loc(_) => Self::Loc,
472 NewFile::PortraitSettings(_, version, entries) => Self::PortraitSettings {
473 version,
474 copy_entries: entries.into_iter().map(|(from, to)| EntryCopy { from, to }).collect(),
475 },
476 NewFile::Text(_, format) => Self::Text { format: Some(format) },
477 NewFile::VMD(_) => Self::Vmd,
478 NewFile::WSModel(_) => Self::WsModel,
479 }
480 }
481}
482
483/// Data source of the files of a file source.
484impl From<&FileSource> for DataSource {
485 fn from(source: &FileSource) -> Self {
486 match source {
487 FileSource::Pack(_) => Self::PackFile,
488 FileSource::GameFiles => Self::GameFiles,
489 FileSource::ParentFiles => Self::ParentFiles,
490 FileSource::AssemblyKit => Self::AssKitFiles,
491 }
492 }
493}
494
495/// Displays the [`DataSource`] as a human-readable string.
496///
497/// Used for logging and UI display purposes.
498impl Display for DataSource {
499 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
500 Display::fmt(match self {
501 Self::PackFile => "PackFile",
502 Self::GameFiles => "GameFiles",
503 Self::ParentFiles => "ParentFiles",
504 Self::AssKitFiles => "AssKitFiles",
505 Self::ExternalFile => "ExternalFile",
506 }, f)
507 }
508}
509
510/// Parses a [`DataSource`] from its string representation.
511///
512/// This is the inverse of the [`Display`] implementation. Panics if the string doesn't match
513/// any known data source.
514impl From<&str> for DataSource {
515 fn from(value: &str) -> Self {
516 match value {
517 "PackFile" => Self::PackFile,
518 "GameFiles" => Self::GameFiles,
519 "ParentFiles" => Self::ParentFiles,
520 "AssKitFiles" => Self::AssKitFiles,
521 "ExternalFile" => Self::ExternalFile,
522 _ => unreachable!("from data source {}", value)
523 }
524 }
525}
526
527impl SessionInfo {
528
529 /// Create a new SessionInfo with the given parameters.
530 pub fn new(
531 session_id: u64,
532 connection_count: u32,
533 timeout_remaining_secs: Option<u64>,
534 is_shutting_down: bool,
535 pack_names: Vec<String>,
536 ) -> Self {
537 Self {
538 session_id,
539 connection_count,
540 timeout_remaining_secs,
541 is_shutting_down,
542 pack_names,
543 }
544 }
545}