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}