Skip to main content

rpfm_server/state/
files.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael 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//! File operations: create, add, delete, move, extract, decode and save files of the open packs,
12//! and read files from every data source.
13
14use anyhow::{anyhow, Result};
15use base64::{Engine, engine::general_purpose::STANDARD};
16
17use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
18use std::env::temp_dir;
19use std::path::{Path, PathBuf};
20use std::slice::from_ref;
21
22use rpfm_extensions::dependencies::Dependencies;
23use rpfm_extensions::optimizer::{OptimizableContainer, OptimizerOptions};
24
25use rpfm_ipc::api::ApiError;
26use rpfm_ipc::api::files::{
27    AddToAnimPack, DeleteFromAnimPack, ExtractFromAnimPack, FileContents, FileData, FileRef, ReadFile, ReadFormat, WriteFile,
28    AddFilesFromDisk, ASSEMBLY_KIT_TABLE_FILE_NAME, CopyFiles, CreateFile, DEFAULT_FILES_LIMIT, DeleteFiles, DuplicateFiles, ExtractFiles,
29    FileEntry, FileList, FileRename, FileSource, FilesAdded, FilesDeleted, FilesExtracted, FilesRenamed, ListFiles, NewFileKind, RenameFiles,
30    FilesPasted, PasteFiles, ViewData,
31};
32use rpfm_ipc::api::tables::GetTableDefinition;
33use rpfm_ipc::api::tools::FilesChanged;
34use rpfm_ipc::helpers::{DataSource, NewFile, RFileInfo};
35
36use rpfm_lib::files::{
37    animpack::AnimPack, Container, ContainerPath, db::DB, DecodeableExtraData, FileType, loc::Loc,
38    pack::{Pack, RESERVED_NAME_NOTES}, portrait_settings::PortraitSettings, RFile, RFileDecoded,
39    text::{Text, TextFormat}, video::SupportedFormats,
40};
41use rpfm_lib::compression::CompressionFormat;
42use rpfm_lib::games::{GameInfo, VanillaDBTableNameLogic};
43use rpfm_lib::schema::Schema;
44
45use super::{ExtractOptions, PasteEntry, SessionState, decode_tables, encode_extra_data, loaded_schema, pack, pack_mut};
46
47/// File types the server never tries to decode.
48const UNDECODEABLE_FILE_TYPES: [FileType; 9] = [
49    FileType::Anim,
50    FileType::BMD,
51    FileType::BMDVegetation,
52    FileType::Dat,
53    FileType::Font,
54    FileType::HlslCompiled,
55    FileType::Pack,
56    FileType::SoundBank,
57    FileType::Unknown,
58];
59
60/// Paths of files, by the key of their pack.
61pub type PathsByPack = BTreeMap<String, Vec<ContainerPath>>;
62
63/// Result of decoding a file.
64#[derive(Debug)]
65pub enum DecodedFile {
66
67    /// The decoded file, and its info.
68    Decoded(Box<RFileDecoded>, RFileInfo),
69
70    /// The notes of a pack, as markdown.
71    Notes(Text),
72
73    /// The file is of a type the server doesn't decode.
74    Unsupported,
75
76    /// The file is external to the server, so it's not decoded.
77    External,
78}
79
80/// Maximum amount of files of a folder listed in the error of reading it as a file.
81const FOLDER_FILES_IN_ERRORS: usize = 10;
82
83impl SessionState {
84
85    /// Creates a new file in a pack.
86    ///
87    /// # Arguments
88    ///
89    /// * `pack_key` - Key of the pack to create the file in.
90    /// * `path` - Path of the new file in the pack.
91    /// * `new_file` - Type and initial data of the new file.
92    ///
93    /// # Errors
94    ///
95    /// Fails if the file is a table with no definition in the schema, or if it can't be inserted.
96    pub fn new_file(&mut self, pack_key: &str, path: &str, new_file: NewFile) -> Result<()> {
97        let decoded = match new_file {
98            NewFile::AnimPack(_) => RFileDecoded::AnimPack(AnimPack::default()),
99            NewFile::DB(_, table, version) => {
100                let schema = loaded_schema(&self.schema)?;
101                let definition = schema.definition_by_name_and_version(&table, version)
102                    .ok_or_else(|| anyhow!("No definitions found for the table `{}`, version `{}` in the currently loaded schema.", table, version))?;
103
104                RFileDecoded::DB(DB::new(definition, schema.patches_for_table(&table), &table))
105            },
106            NewFile::Loc(_) => RFileDecoded::Loc(Loc::new()),
107            NewFile::PortraitSettings(_, version, entries) => {
108                let mut file = PortraitSettings::default();
109                file.set_version(version);
110
111                if !entries.is_empty() {
112                    let mut vanilla_files = self.dependencies.files_by_types_mut(&[FileType::PortraitSettings], true, true);
113                    let vanilla_files_decoded = vanilla_files.iter_mut()
114                        .filter_map(|(_, file)| file.decode(&None, false, true).ok().flatten())
115                        .filter_map(|file| if let RFileDecoded::PortraitSettings(file) = file { Some(file) } else { None })
116                        .collect::<Vec<_>>();
117
118                    let vanilla_values = vanilla_files_decoded.iter()
119                        .flat_map(|file| file.entries())
120                        .map(|entry| (entry.id(), entry))
121                        .collect::<HashMap<_,_>>();
122
123                    for (from_id, to_id) in entries {
124                        if let Some(from_entry) = vanilla_values.get(&from_id) {
125                            let mut new_entry = (*from_entry).clone();
126                            new_entry.set_id(to_id);
127                            file.entries_mut().push(new_entry);
128                        }
129                    }
130                }
131
132                RFileDecoded::PortraitSettings(file)
133            },
134            NewFile::Text(_, text_type) => {
135                let mut file = Text::default();
136                file.set_format(text_type);
137                RFileDecoded::Text(file)
138            },
139            NewFile::VMD(_) => {
140                let mut file = Text::default();
141                file.set_format(TextFormat::Xml);
142                RFileDecoded::VMD(file)
143            },
144            NewFile::WSModel(_) => {
145                let mut file = Text::default();
146                file.set_format(TextFormat::Xml);
147                RFileDecoded::WSModel(file)
148            },
149        };
150
151        let file = RFile::new_from_decoded(&decoded, 0, path);
152        pack_mut(&mut self.packs, pack_key)?.insert(file)?;
153        Ok(())
154    }
155
156    /// Returns a page of the files of a source matching the request, sorted by path.
157    pub fn list_files(&self, request: &ListFiles) -> Result<FileList> {
158        let keep = |path: &str, file_type: FileType| path.starts_with(&request.path_prefix) &&
159            request.file_types.as_ref().is_none_or(|file_types| file_types.contains(&file_type));
160        let entry = |file: &RFile| FileEntry { path: file.path_in_container_raw().to_owned(), file_type: file.file_type() };
161
162        let mut files = match request.source {
163            FileSource::Pack(ref pack_key) => pack(&self.packs, pack_key)?.files().values()
164                .filter(|file| keep(file.path_in_container_raw(), file.file_type()))
165                .map(entry)
166                .collect::<Vec<_>>(),
167            FileSource::GameFiles => self.dependencies.vanilla_loose_files().values()
168                .chain(self.dependencies.vanilla_files().values())
169                .filter(|file| keep(file.path_in_container_raw(), file.file_type()))
170                .map(entry)
171                .collect(),
172            FileSource::ParentFiles => self.dependencies.parent_files().values()
173                .filter(|file| keep(file.path_in_container_raw(), file.file_type()))
174                .map(entry)
175                .collect(),
176            FileSource::AssemblyKit => self.dependencies.asskit_only_db_tables().keys()
177                .map(|table_name| FileEntry { path: format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}"), file_type: FileType::DB })
178                .filter(|file| keep(&file.path, file.file_type))
179                .collect(),
180        };
181
182        // In non-recursive listings, files in subfolders are replaced by their subfolder.
183        let mut folders = BTreeSet::new();
184        if !request.recursive {
185            let base = if request.path_prefix.is_empty() || request.path_prefix.ends_with('/') { request.path_prefix.clone() } else { format!("{}/", request.path_prefix) };
186            files.retain(|file| match file.path.strip_prefix(&base).and_then(|relative| relative.split_once('/')) {
187                Some((folder, _)) => {
188                    folders.insert(format!("{base}{folder}"));
189                    false
190                }
191                None => file.path.starts_with(&base),
192            });
193        }
194
195        files.sort_unstable_by(|a, b| a.path.cmp(&b.path));
196
197        let total = files.len();
198        let files = files.into_iter()
199            .skip(request.offset)
200            .take(request.limit.unwrap_or(DEFAULT_FILES_LIMIT))
201            .collect();
202
203        Ok(FileList { files, folders: folders.into_iter().collect(), total })
204    }
205
206    /// Adds files and folders from disk to a pack.
207    ///
208    /// # Arguments
209    ///
210    /// * `pack_key` - Key of the pack to add the files to.
211    /// * `source_paths` - Paths on disk of the files and folders to add.
212    /// * `destination_paths` - Path in the pack of each source path.
213    /// * `paths_to_ignore` - Source paths under any of these are skipped.
214    /// * `include_base_folder` - If added folders keep their own name in the pack, or only their contents are added.
215    ///
216    /// # Returns
217    ///
218    /// The paths added, and the last error found while adding them, if any.
219    pub fn add_files_from_disk(&mut self, pack_key: &str, source_paths: &[PathBuf], destination_paths: &[ContainerPath], paths_to_ignore: &Option<Vec<PathBuf>>, include_base_folder: bool) -> Result<(Vec<ContainerPath>, Option<String>)> {
220        let pack = pack_mut(&mut self.packs, pack_key)?;
221        let mut added_paths = vec![];
222        let mut last_error = None;
223
224        for (source_path, destination_path) in source_paths.iter().zip(destination_paths.iter()) {
225            if let Some(ref paths_to_ignore) = paths_to_ignore {
226                if paths_to_ignore.iter().any(|path| source_path.starts_with(path)) {
227                    continue;
228                }
229            }
230
231            match destination_path {
232                ContainerPath::File(destination_path) => match pack.insert_file(source_path, destination_path, &self.schema) {
233                    Ok(path) => added_paths.extend(path),
234                    Err(error) => last_error = Some(error.to_string()),
235                },
236                ContainerPath::Folder(destination_path) => match pack.insert_folder(source_path, destination_path, &None, &self.schema, include_base_folder) {
237                    Ok(mut paths) => added_paths.append(&mut paths),
238                    Err(error) => last_error = Some(error.to_string()),
239                },
240            }
241        }
242
243        decode_tables(&mut pack.files_by_paths_mut(&added_paths, false), &self.schema);
244        Ok((added_paths, last_error))
245    }
246
247    /// Copies files from one open pack to another.
248    ///
249    /// # Arguments
250    ///
251    /// * `target_key` - Key of the pack to copy the files to.
252    /// * `source_key` - Key of the pack to copy the files from.
253    /// * `paths` - Paths of the files and folders to copy.
254    ///
255    /// # Returns
256    ///
257    /// The paths added to the target pack, the paths of the files that couldn't be read, and the last error reading them.
258    pub fn add_files_from_pack(&mut self, target_key: &str, source_key: &str, paths: &[ContainerPath]) -> Result<(Vec<ContainerPath>, Vec<String>, Option<String>)> {
259        let (files, not_loaded, error) = loaded_files_by_paths(pack(&self.packs, source_key).map_err(|_| anyhow!("Source pack not found: {}", source_key))?, paths);
260        let target_pack = pack_mut(&mut self.packs, target_key).map_err(|_| anyhow!("Target pack not found: {}", target_key))?;
261
262        let added_paths = insert_files(target_pack, files);
263        decode_tables(&mut target_pack.files_by_paths_mut(&added_paths, false), &self.schema);
264        Ok((added_paths, not_loaded, error))
265    }
266
267    /// Copies files from an open pack into an AnimPack of another (or the same) open pack.
268    ///
269    /// # Arguments
270    ///
271    /// * `source_pack_key` - Key of the pack to copy the files from.
272    /// * `anim_pack_key` - Key of the pack containing the AnimPack.
273    /// * `anim_pack_path` - Path of the AnimPack in its pack.
274    /// * `paths` - Paths of the files and folders to copy.
275    ///
276    /// # Returns
277    ///
278    /// The paths added to the AnimPack, the paths of the files that couldn't be read, and the last error reading them.
279    pub fn add_files_to_animpack(&mut self, source_pack_key: &str, anim_pack_key: &str, anim_pack_path: &str, paths: &[ContainerPath]) -> Result<(Vec<ContainerPath>, Vec<String>, Option<String>)> {
280        let (files, not_loaded, error) = loaded_files_by_paths(pack(&self.packs, source_pack_key)?, paths);
281        let anim_pack_file = pack_mut(&mut self.packs, anim_pack_key)?.files_mut()
282            .get_mut(anim_pack_path)
283            .ok_or_else(|| anyhow!("File not found in the Pack: {}.", anim_pack_path))?;
284
285        let anim_pack = decoded_anim_pack(anim_pack_file, anim_pack_path)?;
286        let added_paths = files.into_iter()
287            .filter_map(|file| anim_pack.insert(file).ok().flatten())
288            .collect();
289
290        Ok((added_paths, not_loaded, error))
291    }
292
293    /// Copies files from an AnimPack into an open pack.
294    ///
295    /// # Arguments
296    ///
297    /// * `anim_pack_key` - Key of the pack containing the AnimPack. Only used if `data_source` is a pack.
298    /// * `dest_pack_key` - Key of the pack to copy the files to.
299    /// * `data_source` - Where the AnimPack is.
300    /// * `anim_pack_path` - Path of the AnimPack in its source.
301    /// * `paths` - Paths in the AnimPack of the files and folders to copy.
302    ///
303    /// # Returns
304    ///
305    /// The paths of the copied files.
306    pub fn add_files_from_animpack(&mut self, anim_pack_key: &str, dest_pack_key: &str, data_source: DataSource, anim_pack_path: &str, paths: &[ContainerPath]) -> Result<Vec<ContainerPath>> {
307        let anim_pack_file = match data_source {
308            DataSource::PackFile => self.packs.get_mut(anim_pack_key).and_then(|pack| pack.files_mut().get_mut(anim_pack_path)),
309            DataSource::GameFiles => self.dependencies.file_mut(anim_pack_path, true, false).ok(),
310            DataSource::ParentFiles => self.dependencies.file_mut(anim_pack_path, false, true).ok(),
311            DataSource::AssKitFiles |
312            DataSource::ExternalFile => return Err(anyhow!("You can't import files from this source.")),
313        }.ok_or_else(|| anyhow!("The file with the path {} doesn't exists on the open Pack.", anim_pack_path))?;
314
315        let files = decoded_anim_pack(anim_pack_file, anim_pack_path)?
316            .files_by_paths(paths, false)
317            .into_iter()
318            .cloned()
319            .collect::<Vec<RFile>>();
320
321        let result_paths = files.iter().map(|file| file.path_in_container()).collect::<Vec<_>>();
322        let pack = pack_mut(&mut self.packs, dest_pack_key)?;
323        for mut file in files {
324            let _ = file.guess_file_type();
325            let _ = pack.insert(file);
326        }
327
328        Ok(result_paths)
329    }
330
331    /// Deletes files from an AnimPack in an open pack.
332    pub fn delete_from_animpack(&mut self, pack_key: &str, anim_pack_path: &str, paths: &[ContainerPath]) -> Result<()> {
333        let anim_pack_file = pack_mut(&mut self.packs, pack_key)?.files_mut()
334            .get_mut(anim_pack_path)
335            .ok_or_else(|| anyhow!("File not found in the Pack: {}.", anim_pack_path))?;
336
337        let anim_pack = decoded_anim_pack(anim_pack_file, anim_pack_path)?;
338        for path in paths {
339            anim_pack.remove(path);
340        }
341
342        Ok(())
343    }
344
345    /// Decodes a file as a client opening it in a view needs it.
346    ///
347    /// # Arguments
348    ///
349    /// * `file` - The file.
350    /// * `enable_esf_editor` - If ESF files are decoded.
351    pub fn view_data(&mut self, file: &FileRef, enable_esf_editor: bool) -> Result<ViewData> {
352        let (pack_key, data_source) = pack_key_and_data_source(&file.source);
353        let pack_key = pack_key.to_owned();
354        let (decoded, info) = match self.decode_file(&pack_key, &file.path, data_source, enable_esf_editor)? {
355            DecodedFile::Decoded(decoded, info) => (decoded, info),
356            DecodedFile::Notes(notes) => return Ok(ViewData::Notes(notes)),
357            DecodedFile::Unsupported | DecodedFile::External => return Ok(ViewData::Unsupported),
358        };
359
360        Ok(match *decoded {
361            RFileDecoded::AnimPack(data) => ViewData::AnimPack(data.files().values().map(From::from).collect(), info),
362            RFileDecoded::Video(data) => ViewData::Video(From::from(&data), info),
363            RFileDecoded::Anim(_) |
364            RFileDecoded::BMD(_) |
365            RFileDecoded::BMDVegetation(_) |
366            RFileDecoded::Dat(_) |
367            RFileDecoded::Font(_) |
368            RFileDecoded::HlslCompiled(_) |
369            RFileDecoded::Pack(_) |
370            RFileDecoded::SoundBank(_) |
371            RFileDecoded::TradeRoutes(_) |
372            RFileDecoded::Unknown(_) => ViewData::Unsupported,
373            decoded @ (RFileDecoded::AnimFragmentBattle(_) |
374                RFileDecoded::AnimsTable(_) |
375                RFileDecoded::Atlas(_) |
376                RFileDecoded::Audio(_) |
377                RFileDecoded::DB(_) |
378                RFileDecoded::ESF(_) |
379                RFileDecoded::GroupFormations(_) |
380                RFileDecoded::Image(_) |
381                RFileDecoded::Loc(_) |
382                RFileDecoded::MatchedCombat(_) |
383                RFileDecoded::PortraitSettings(_) |
384                RFileDecoded::RigidModel(_) |
385                RFileDecoded::Text(_) |
386                RFileDecoded::UIC(_) |
387                RFileDecoded::UnitVariant(_) |
388                RFileDecoded::VMD(_) |
389                RFileDecoded::WSModel(_)) => ViewData::Decoded(decoded, info),
390        })
391    }
392
393    /// Decodes a file.
394    ///
395    /// # Arguments
396    ///
397    /// * `pack_key` - Key of the pack containing the file. Only used if `data_source` is a pack.
398    /// * `path` - Path of the file in its source.
399    /// * `data_source` - Where the file is.
400    /// * `enable_esf_editor` - If ESF files are decoded.
401    ///
402    /// # Returns
403    ///
404    /// The decoded file, or why it wasn't decoded.
405    pub fn decode_file(&mut self, pack_key: &str, path: &str, data_source: DataSource, enable_esf_editor: bool) -> Result<DecodedFile> {
406        match data_source {
407            DataSource::PackFile => {
408                let pack = pack_mut(&mut self.packs, pack_key)?;
409                if path == RESERVED_NAME_NOTES {
410                    let mut notes = Text::default();
411                    notes.set_format(TextFormat::Markdown);
412                    notes.set_contents(pack.notes().pack_notes().to_owned());
413                    return Ok(DecodedFile::Notes(notes));
414                }
415
416                let file = pack.files_mut().get_mut(path)
417                    .ok_or_else(|| anyhow!("The file with the path {} hasn't been found on this Pack.", path))?;
418                decode(file, &self.game, &self.schema, enable_esf_editor)
419            }
420            DataSource::ParentFiles => decode(self.dependencies.file_mut(path, false, true)?, &self.game, &self.schema, enable_esf_editor),
421            DataSource::GameFiles => decode(self.dependencies.file_mut(path, true, false)?, &self.game, &self.schema, enable_esf_editor),
422            DataSource::AssKitFiles => {
423                let table_name = path.split('/').nth(1)
424                    .filter(|_| path.split('/').count() > 2)
425                    .ok_or_else(|| anyhow!("Path {} doesn't contain an identifiable table name.", path))?;
426
427                let table = self.dependencies.asskit_only_db_tables().get(table_name)
428                    .ok_or_else(|| anyhow!("Table {} not found on Assembly Kit files.", path))?;
429
430                Ok(DecodedFile::Decoded(Box::new(RFileDecoded::DB(table.clone())), RFileInfo::default()))
431            }
432            DataSource::ExternalFile => Ok(DecodedFile::External),
433        }
434    }
435
436    /// Replaces the data of a file of a pack with its edited version.
437    pub fn save_file_from_view(&mut self, pack_key: &str, path: &str, decoded: RFileDecoded) -> Result<()> {
438        let pack = pack_mut(&mut self.packs, pack_key)?;
439        if path == RESERVED_NAME_NOTES {
440            if let RFileDecoded::Text(data) = decoded {
441                pack.notes_mut().set_pack_notes(data.contents().to_owned());
442            }
443        } else if let Some(file) = pack.files_mut().get_mut(path) {
444            file.set_decoded(decoded)?;
445        }
446
447        Ok(())
448    }
449
450    /// Deletes files and folders from a pack.
451    ///
452    /// # Returns
453    ///
454    /// The paths deleted.
455    pub fn delete_files(&mut self, pack_key: &str, paths: &[ContainerPath]) -> Result<Vec<ContainerPath>> {
456        let pack = pack_mut(&mut self.packs, pack_key)?;
457        Ok(paths.iter().flat_map(|path| pack.remove(path)).collect())
458    }
459
460    /// Copies or moves files and folders of open packs into a folder of an open pack. Cut files are removed from
461    /// their packs before pasting, so they can be pasted where they were.
462    ///
463    /// # Returns
464    ///
465    /// The paths added to the target pack, and the paths removed from each source pack if the files were cut.
466    pub fn paste_files(&mut self, request: &PasteFiles) -> Result<FilesPasted> {
467        let target_key = &request.pack;
468        pack(&self.packs, target_key)?;
469
470        let mut entries = vec![];
471        for (pack_key, paths) in &request.sources {
472            let source_pack = pack(&self.packs, pack_key)?;
473            let paths = paths.iter().map(|path| container_path(|path| source_pack.has_file(path), path)).collect::<Vec<_>>();
474            entries.extend(paste_entries_from_paths(source_pack, &paths, pack_key));
475        }
476
477        if entries.is_empty() {
478            return Err(ApiError::InvalidParams("There are no files to paste.".to_owned()).into());
479        }
480
481        // Clone the files first, so we don't hold borrows of their packs while mutating them.
482        let destination_path = request.folder.trim_end_matches('/');
483        let mut files_to_insert = Vec::with_capacity(entries.len());
484        let mut pasted_entries = Vec::with_capacity(entries.len());
485        let mut not_pasted = vec![];
486        let mut last_error = None;
487        for entry in &entries {
488            let Some(source_pack) = self.packs.get(&entry.source_pack_key) else { continue };
489            let Some(file) = source_pack.files_by_paths(&[ContainerPath::File(entry.file_path.clone())], false).first().copied() else { continue };
490
491            // Unloaded copies would keep reading from the source pack, and fail once it changes on disk.
492            let mut new_file = file.clone();
493            if let Err(error) = new_file.load() {
494                last_error = Some(format!("{}: {error}", entry.file_path));
495                not_pasted.push(entry.file_path.clone());
496                continue;
497            }
498
499            let relative_path = if !entry.base_path.is_empty() && entry.file_path.starts_with(&entry.base_path) {
500                entry.file_path[entry.base_path.len()..].trim_start_matches('/')
501            } else {
502                &entry.file_path
503            };
504
505            let new_path = if destination_path.is_empty() {
506                relative_path.to_string()
507            } else {
508                format!("{destination_path}/{relative_path}")
509            };
510
511            new_file.set_path_in_container_raw(&new_path);
512            files_to_insert.push(new_file);
513            pasted_entries.push(entry);
514        }
515
516        let mut deleted: BTreeMap<String, Vec<String>> = BTreeMap::new();
517        if request.cut {
518            for entry in pasted_entries {
519                if let Some(source_pack) = self.packs.get_mut(&entry.source_pack_key) {
520                    let removed = source_pack.remove(&ContainerPath::File(entry.file_path.clone()));
521                    deleted.entry(entry.source_pack_key.clone()).or_default().extend(raw_paths(&removed));
522                }
523            }
524        }
525
526        let target_pack = pack_mut(&mut self.packs, target_key)?;
527        let added_paths = insert_files(target_pack, files_to_insert);
528        decode_tables(&mut target_pack.files_by_paths_mut(&added_paths, false), &self.schema);
529
530        Ok(FilesPasted { added: raw_paths(&added_paths), deleted, not_pasted, error: last_error })
531    }
532
533    /// Duplicates files in the same pack, adding a numeric suffix to their names.
534    ///
535    /// # Returns
536    ///
537    /// The paths of the duplicates.
538    pub fn duplicate_files(&mut self, pack_key: &str, paths: &[ContainerPath]) -> Result<Vec<ContainerPath>> {
539        let pack = pack_mut(&mut self.packs, pack_key)?;
540        let files_to_duplicate = pack.files_by_paths(paths, false)
541            .into_iter()
542            .cloned()
543            .collect::<Vec<RFile>>();
544
545        let mut added_paths = Vec::with_capacity(files_to_duplicate.len());
546        for mut file in files_to_duplicate {
547            let new_path = duplicate_path(pack, file.path_in_container_raw());
548            file.set_path_in_container_raw(&new_path);
549
550            if let Ok(Some(path)) = pack.insert(file) {
551                added_paths.push(path);
552            }
553        }
554
555        decode_tables(&mut pack.files_by_paths_mut(&added_paths, false), &self.schema);
556        Ok(added_paths)
557    }
558
559    /// Extracts files from a pack or from the dependencies to disk.
560    ///
561    /// # Arguments
562    ///
563    /// * `pack_key` - Key of the pack to extract from. Only used for paths from a pack.
564    /// * `paths_by_source` - Paths to extract, by source. If there are paths from a pack, only those are extracted.
565    /// * `destination` - Folder to extract the files to.
566    /// * `as_tsv` - If tables are extracted as TSV.
567    /// * `options` - Options for writing the files.
568    ///
569    /// # Returns
570    ///
571    /// The paths on disk of the extracted files.
572    ///
573    /// # Errors
574    ///
575    /// Fails if any file couldn't be extracted.
576    pub fn extract_files(&mut self, pack_key: &str, paths_by_source: &BTreeMap<DataSource, Vec<ContainerPath>>, destination: &Path, as_tsv: bool, options: ExtractOptions) -> Result<Vec<PathBuf>> {
577        let no_schema = None;
578        let schema = if as_tsv { &self.schema } else { &no_schema };
579        let mut errors = 0;
580        let mut extracted_paths = vec![];
581
582        if let Some(container_paths) = paths_by_source.get(&DataSource::PackFile) {
583            let pack = pack_mut(&mut self.packs, pack_key)?;
584            let extra_data = encode_extra_data(&self.game, pack.compression_format(), options.disable_uuid_regeneration);
585
586            for container_path in container_paths {
587                match pack.extract(container_path.clone(), destination, true, schema, false, options.tsv_keys_first, &extra_data) {
588                    Ok(mut paths) => extracted_paths.append(&mut paths),
589                    Err(_) => errors += 1,
590                }
591            }
592        }
593
594        else {
595            let mut files = match paths_by_source.get(&DataSource::GameFiles) {
596                Some(paths) => self.dependencies.files_by_path(paths, true, false, false),
597                None => HashMap::new(),
598            };
599
600            if let Some(paths) = paths_by_source.get(&DataSource::ParentFiles) {
601                files.extend(self.dependencies.files_by_path(paths, false, true, false));
602            }
603
604            let mut pack = Pack::default();
605            let extra_data = encode_extra_data(&self.game, pack.compression_format(), options.disable_uuid_regeneration);
606            for (path_raw, file) in files {
607                if pack.insert(file.clone()).is_err() {
608                    errors += 1;
609                    continue;
610                }
611
612                let container_path = ContainerPath::File(path_raw);
613                match pack.extract(container_path.clone(), destination, true, schema, false, options.tsv_keys_first, &extra_data) {
614                    Ok(mut paths) => extracted_paths.append(&mut paths),
615                    Err(_) => errors += 1,
616                }
617
618                // Drop the cloned file from the temp pack so memory doesn't grow with the batch.
619                pack.remove(&container_path);
620            }
621        }
622
623        if errors == 0 {
624            Ok(extracted_paths)
625        } else {
626            Err(anyhow!("There were {} errors while extracting.", errors))
627        }
628    }
629
630    /// Renames or moves files and folders in a pack.
631    ///
632    /// # Returns
633    ///
634    /// The old and new path of each moved file.
635    pub fn rename_files(&mut self, pack_key: &str, renames: &[(ContainerPath, ContainerPath)]) -> Result<Vec<(ContainerPath, ContainerPath)>> {
636        Ok(pack_mut(&mut self.packs, pack_key)?.move_paths(renames)?)
637    }
638
639    /// Returns if a file exists in a pack.
640    pub fn file_exists(&self, pack_key: &str, path: &str) -> Result<bool> {
641        Ok(pack(&self.packs, pack_key)?.has_file(path))
642    }
643
644    /// Extracts a file of a pack to a temp folder and opens it in the system's default program for its type.
645    ///
646    /// # Returns
647    ///
648    /// The path of the extracted file.
649    ///
650    /// # Errors
651    ///
652    /// Fails if the pack isn't open, or if the file can't be extracted.
653    pub fn open_in_external_program(&mut self, pack_key: &str, path: &str, options: ExtractOptions) -> Result<PathBuf> {
654        let pack = pack_mut(&mut self.packs, pack_key)?;
655        let folder = temp_dir().join(format!("rpfm_{}", pack.disk_file_name()));
656        let extra_data = encode_extra_data(&self.game, pack.compression_format(), options.disable_uuid_regeneration);
657
658        let extracted_paths = pack.extract(ContainerPath::File(path.to_owned()), &folder, true, &self.schema, false, options.tsv_keys_first, &extra_data)?;
659        let extracted_path = extracted_paths.first()
660            .ok_or_else(|| anyhow!("Nothing was extracted from {path}."))?;
661
662        let _ = open::that(extracted_path);
663        Ok(extracted_path.to_owned())
664    }
665
666    /// Replaces a file of a pack with the data of a file on disk, edited by an external program.
667    pub fn save_file_from_external(&mut self, pack_key: &str, path: &str, external_path: &Path) -> Result<()> {
668        let file = pack_mut(&mut self.packs, pack_key)?.file_mut(path, false)
669            .ok_or_else(|| anyhow!("File not found"))?;
670
671        file.encode_from_external_data(&self.schema, external_path)?;
672        Ok(())
673    }
674
675    /// Adds files edited by the client to a pack, replacing existing ones, and optionally optimizes the pack.
676    ///
677    /// # Arguments
678    ///
679    /// * `pack_key` - Key of the pack to add the files to.
680    /// * `files` - Files to add, already named with their path in the pack.
681    /// * `optimizer_options` - If set, the pack is optimized with these options after adding the files.
682    ///
683    /// # Returns
684    ///
685    /// The paths added, and the paths deleted by the optimizer.
686    pub fn save_files_and_optimize(&mut self, pack_key: &str, files: Vec<RFile>, optimizer_options: Option<OptimizerOptions>) -> Result<FilesChanged> {
687        let pack = pack_mut(&mut self.packs, pack_key)?;
688        let schema = loaded_schema(&self.schema)?;
689
690        let mut added_paths = insert_files(pack, files);
691        added_paths.sort();
692        added_paths.dedup();
693
694        let mut added = raw_paths(&added_paths);
695        let Some(options) = optimizer_options else {
696            return Ok(FilesChanged { added, deleted: vec![] });
697        };
698
699        let (paths_to_delete, paths_to_add) = pack.optimize(None, &mut self.dependencies, schema, &self.game, &options)?;
700        added.extend(paths_to_add);
701        Ok(FilesChanged { added, deleted: paths_to_delete.into_iter().collect() })
702    }
703
704    /// Changes the format of a ca_vp8 video of a pack.
705    pub fn set_video_format(&mut self, pack_key: &str, path: &str, format: SupportedFormats) -> Result<()> {
706        let file = pack_mut(&mut self.packs, pack_key)?.files_mut()
707            .get_mut(path)
708            .ok_or_else(|| anyhow!("File not found in the Pack: {}.", path))?;
709
710        match file.decoded_mut()? {
711            RFileDecoded::Video(data) => {
712                data.set_format(format);
713                Ok(())
714            }
715            _ => Err(anyhow!("The file is not a video.")),
716        }
717    }
718
719    /// Returns the files at the provided paths from the open packs, the parent packs and the game files.
720    ///
721    /// # Arguments
722    ///
723    /// * `paths` - Paths of the files and folders to get.
724    /// * `lowercase_paths` - If the returned paths are lowercased.
725    ///
726    /// # Returns
727    ///
728    /// The files found, by source and path.
729    pub fn files_from_all_sources(&self, paths: &[ContainerPath], lowercase_paths: bool) -> HashMap<DataSource, HashMap<String, RFile>> {
730        let normalize = |path: String| if lowercase_paths { path.to_lowercase() } else { path };
731
732        let parent_files = self.dependencies.files_by_path(paths, false, true, true).into_iter()
733            .map(|(path, file)| (normalize(path), file.clone()))
734            .collect();
735
736        let game_files = self.dependencies.files_by_path(paths, true, false, true).into_iter()
737            .map(|(path, file)| (normalize(path), file.clone()))
738            .collect();
739
740        let pack_files = self.packs.values()
741            .flat_map(|pack| pack.files_by_paths(paths, true))
742            .map(|file| (normalize(file.path_in_container_raw().to_owned()), file.clone()))
743            .collect();
744
745        HashMap::from([
746            (DataSource::ParentFiles, parent_files),
747            (DataSource::GameFiles, game_files),
748            (DataSource::PackFile, pack_files),
749        ])
750    }
751
752    /// Returns the paths of the animations using a skeleton, from the open packs, the parent packs and the game files.
753    pub fn anim_paths_by_skeleton(&mut self, skeleton_name: &str) -> HashSet<String> {
754        let mut paths = HashSet::new();
755
756        for (game_files, parent_files) in [(true, false), (false, true)] {
757            for (path, file) in self.dependencies.files_by_types_mut(&[FileType::Anim], game_files, parent_files) {
758                if let Ok(Some(RFileDecoded::Anim(file))) = file.decode(&None, false, true) {
759                    if file.skeleton_name() == skeleton_name {
760                        paths.insert(path);
761                    }
762                }
763            }
764        }
765
766        for pack in self.packs.values_mut() {
767            for file in pack.files_by_type_mut(&[FileType::Anim]) {
768                if let Ok(Some(RFileDecoded::Anim(anim_file))) = file.decode(&None, false, true) {
769                    if anim_file.skeleton_name() == skeleton_name {
770                        paths.insert(file.path_in_container_raw().to_owned());
771                    }
772                }
773            }
774        }
775
776        paths
777    }
778
779    /// Copies files from the dependencies (game files, parent packs or Assembly Kit tables) into a pack.
780    ///
781    /// # Arguments
782    ///
783    /// * `pack_key` - Key of the pack to copy the files into.
784    /// * `paths_by_source` - Paths of the files and folders to copy, by source.
785    ///
786    /// # Returns
787    ///
788    /// The paths added, the paths that couldn't be added, and the last error found reading them.
789    pub fn import_dependencies(&mut self, pack_key: &str, paths_by_source: &BTreeMap<DataSource, Vec<ContainerPath>>) -> Result<(Vec<ContainerPath>, Vec<String>, Option<String>)> {
790        let pack = pack_mut(&mut self.packs, pack_key)?;
791        let mut added_paths = vec![];
792        let mut not_added_paths = vec![];
793        let mut last_error = None;
794
795        for (data_source, paths) in paths_by_source {
796            let files = match data_source {
797                DataSource::GameFiles => self.dependencies.files_by_path(paths, true, false, false),
798                DataSource::ParentFiles => self.dependencies.files_by_path(paths, false, true, false),
799                DataSource::AssKitFiles => continue,
800                DataSource::PackFile |
801                DataSource::ExternalFile => return Err(anyhow!("You can't import files from this source.")),
802            };
803
804            for file in files.into_values() {
805                let mut file = file.clone();
806                let file_path = file.path_in_container_raw().to_owned();
807
808                // Unloaded copies would keep reading from the dependency's pack, and fail once it changes on disk.
809                if let Err(error) = file.load() {
810                    last_error = Some(format!("{file_path}: {error}"));
811                    not_added_paths.push(file_path);
812                    continue;
813                }
814
815                let _ = file.guess_file_type();
816                match pack.insert(file) {
817                    Ok(Some(path)) => added_paths.push(path),
818                    _ => not_added_paths.push(file_path),
819                }
820            }
821        }
822
823        // Once we're done with normal files, we process the Assembly Kit ones.
824        if let Some(paths) = paths_by_source.get(&DataSource::AssKitFiles) {
825            let schema = loaded_schema(&self.schema)?;
826            let table_name_logic = self.game.vanilla_db_table_name_logic();
827            let mut files = vec![];
828
829            for path in paths {
830                match path {
831
832                    // We only have tables. If it's a folder, it's either a table folder, db or the root.
833                    ContainerPath::Folder(path) => {
834                        let path = path.strip_suffix('/').unwrap_or(path);
835                        let path_split = path.split('/').collect::<Vec<_>>();
836
837                        let table_names = match path_split.len() {
838                            1 => self.dependencies.asskit_only_db_tables().keys().map(|name| name.as_str()).collect::<Vec<_>>(),
839                            2 => vec![path_split[1]],
840                            _ => return Err(anyhow!("No idea how you were able to trigger this.")),
841                        };
842
843                        for table_name in table_names {
844                            let table_file_name = match table_name_logic {
845                                VanillaDBTableNameLogic::DefaultName(name) => name.as_str(),
846                                VanillaDBTableNameLogic::FolderName => table_name,
847                            };
848
849                            let mut file_path = path_split.clone();
850                            file_path.push(table_file_name);
851                            match import_asskit_table(&self.dependencies, schema, table_name, &file_path.join("/")) {
852                                Some(file) => files.push(file),
853                                None => not_added_paths.push(path.to_owned()),
854                            }
855                        }
856                    }
857                    ContainerPath::File(path) => {
858                        let table_name = path.split('/').nth(1).unwrap_or_default();
859                        match import_asskit_table(&self.dependencies, schema, table_name, path) {
860                            Some(file) => files.push(file),
861                            None => not_added_paths.push(path.clone()),
862                        }
863                    }
864                }
865            }
866
867            added_paths.append(&mut insert_files(pack, files));
868        }
869
870        Ok((added_paths, not_added_paths, last_error))
871    }
872}
873
874impl SessionState {
875
876    /// Creates a new empty file in an open pack.
877    ///
878    /// # Returns
879    ///
880    /// The path and type of the new file.
881    pub fn create_file(&mut self, request: &CreateFile) -> Result<FileEntry> {
882        let new_file = match request.kind {
883            NewFileKind::Db { ref table_name, version } => {
884                let definition = self.table_definition(&GetTableDefinition { table_name: table_name.clone(), version })?;
885                NewFile::DB(request.path.clone(), table_name.clone(), definition.version)
886            }
887            NewFileKind::Loc => NewFile::Loc(request.path.clone()),
888            NewFileKind::Text { format } => NewFile::Text(request.path.clone(), format.unwrap_or(TextFormat::Plain)),
889            NewFileKind::AnimPack => NewFile::AnimPack(request.path.clone()),
890            NewFileKind::PortraitSettings { version, ref copy_entries } => {
891                let entries = copy_entries.iter().map(|entry| (entry.from.clone(), entry.to.clone())).collect();
892                NewFile::PortraitSettings(request.path.clone(), version, entries)
893            }
894            NewFileKind::Vmd => NewFile::VMD(request.path.clone()),
895            NewFileKind::WsModel => NewFile::WSModel(request.path.clone()),
896        };
897
898        self.new_file(&request.pack, &request.path, new_file)?;
899
900        let file = pack(&self.packs, &request.pack)?.files().get(&request.path)
901            .ok_or_else(|| ApiError::FileNotFound(request.path.clone()))?;
902        Ok(FileEntry { path: file.path_in_container_raw().to_owned(), file_type: file.file_type() })
903    }
904
905    /// Adds files and folders from disk to an open pack, under a folder of the pack.
906    ///
907    /// # Arguments
908    ///
909    /// * `request` - What to add, and where.
910    /// * `include_base_folder` - If added folders keep their own name in the pack.
911    pub fn add_disk_files(&mut self, request: &AddFilesFromDisk, include_base_folder: bool) -> Result<FilesAdded> {
912        let destination_paths = match request.pack_paths {
913            Some(ref destinations) => {
914                if destinations.len() != request.paths.len() {
915                    return Err(ApiError::InvalidParams("There must be one destination per path.".to_owned()).into());
916                }
917
918                request.paths.iter().zip(destinations)
919                    .map(|(path, destination)| if path.is_file() {
920                        ContainerPath::File(destination.to_owned())
921                    } else {
922                        ContainerPath::Folder(destination.trim_end_matches('/').to_owned())
923                    })
924                    .collect::<Vec<_>>()
925            }
926            None => {
927                let destination = request.folder.trim_end_matches('/');
928                request.paths.iter()
929                    .map(|path| match path.file_name().filter(|_| path.is_file()) {
930                        Some(name) if destination.is_empty() => ContainerPath::File(name.to_string_lossy().to_string()),
931                        Some(name) => ContainerPath::File(format!("{destination}/{}", name.to_string_lossy())),
932                        None => ContainerPath::Folder(destination.to_owned()),
933                    })
934                    .collect::<Vec<_>>()
935            }
936        };
937
938        let ignore = if request.ignore.is_empty() { None } else { Some(request.ignore.clone()) };
939        let (added, error) = self.add_files_from_disk(&request.pack, &request.paths, &destination_paths, &ignore, include_base_folder)?;
940        Ok(FilesAdded { added: raw_paths(&added), not_added: vec![], error })
941    }
942
943    /// Copies files and folders from any source into an open pack, keeping their paths.
944    ///
945    /// Assembly Kit tables get the file name tables have in the game files.
946    pub fn copy_files_to_pack(&mut self, request: &CopyFiles) -> Result<FilesAdded> {
947        match request.source {
948            FileSource::Pack(ref source_key) => {
949                let source = pack(&self.packs, source_key)?;
950                let (paths, not_found) = split_found(&request.paths, |path| container_path(|path| source.has_file(path), path), |path| !source.files_by_path(path, false).is_empty());
951
952                let (added, mut not_added, error) = self.add_files_from_pack(&request.pack, source_key, &paths)?;
953                not_added.extend(not_found);
954                Ok(FilesAdded { added: raw_paths(&added), not_added, error })
955            }
956            FileSource::GameFiles | FileSource::ParentFiles => {
957                let data_source = DataSource::from(&request.source);
958                let (include_vanilla, include_parent) = (data_source == DataSource::GameFiles, data_source == DataSource::ParentFiles);
959                let (paths, mut not_found) = split_found(
960                    &request.paths,
961                    |path| container_path(|path| self.dependencies.file_exists(path, include_vanilla, include_parent, false), path),
962                    |path| !self.dependencies.files_by_path(from_ref(path), include_vanilla, include_parent, false).is_empty(),
963                );
964
965                let (added, mut not_added, error) = self.import_dependencies(&request.pack, &BTreeMap::from([(data_source, paths)]))?;
966                not_added.append(&mut not_found);
967                Ok(FilesAdded { added: raw_paths(&added), not_added, error })
968            }
969            FileSource::AssemblyKit => {
970
971                // Their listed paths end in a placeholder file name, so they're copied as their table folder instead.
972                let paths = request.paths.iter()
973                    .map(|path| ContainerPath::Folder(path.strip_suffix(ASSEMBLY_KIT_TABLE_FILE_NAME).unwrap_or(path).trim_end_matches('/').to_owned()))
974                    .collect();
975
976                let (added, not_added, error) = self.import_dependencies(&request.pack, &BTreeMap::from([(DataSource::AssKitFiles, paths)]))?;
977                Ok(FilesAdded { added: raw_paths(&added), not_added, error })
978            }
979        }
980    }
981
982    /// Deletes files and folders from an open pack.
983    pub fn delete_paths(&mut self, request: &DeleteFiles) -> Result<FilesDeleted> {
984        let paths = self.pack_container_paths(&request.pack, &request.paths)?;
985        let deleted = self.delete_files(&request.pack, &paths)?;
986        Ok(FilesDeleted { deleted: raw_paths(&deleted) })
987    }
988
989    /// Renames or moves files and folders of an open pack.
990    pub fn rename_paths(&mut self, request: &RenameFiles) -> Result<FilesRenamed> {
991        let pack = pack(&self.packs, &request.pack)?;
992        let renames = request.renames.iter()
993            .map(|rename| match container_path(|path| pack.has_file(path), &rename.from) {
994                ContainerPath::File(from) => (ContainerPath::File(from), ContainerPath::File(rename.to.clone())),
995                ContainerPath::Folder(from) => (ContainerPath::Folder(from), ContainerPath::Folder(rename.to.clone())),
996            })
997            .collect::<Vec<_>>();
998
999        let renamed = self.rename_files(&request.pack, &renames)?;
1000        Ok(FilesRenamed {
1001            renamed: renamed.iter()
1002                .map(|(from, to)| FileRename { from: from.path_raw().to_owned(), to: to.path_raw().to_owned() })
1003                .collect(),
1004        })
1005    }
1006
1007    /// Duplicates files of an open pack in the same pack.
1008    pub fn duplicate_paths(&mut self, request: &DuplicateFiles) -> Result<FilesAdded> {
1009        let paths = self.pack_container_paths(&request.pack, &request.paths)?;
1010        let added = self.duplicate_files(&request.pack, &paths)?;
1011        Ok(FilesAdded { added: raw_paths(&added), ..FilesAdded::default() })
1012    }
1013
1014    /// Extracts files and folders of an open pack, the game files or the parent packs to disk.
1015    ///
1016    /// # Arguments
1017    ///
1018    /// * `request` - What to extract, and where.
1019    /// * `options` - Options for writing the files.
1020    pub fn extract_paths(&mut self, request: &ExtractFiles, options: ExtractOptions) -> Result<FilesExtracted> {
1021        let (pack_key, data_source, paths) = match request.source {
1022            FileSource::Pack(ref pack_key) => (pack_key.as_str(), DataSource::PackFile, self.pack_container_paths(pack_key, &request.paths)?),
1023            FileSource::GameFiles | FileSource::ParentFiles => {
1024                let data_source = DataSource::from(&request.source);
1025                let (include_vanilla, include_parent) = (data_source == DataSource::GameFiles, data_source == DataSource::ParentFiles);
1026                let paths = request.paths.iter()
1027                    .map(|path| container_path(|path| self.dependencies.file_exists(path, include_vanilla, include_parent, false), path))
1028                    .collect();
1029
1030                ("", data_source, paths)
1031            }
1032            FileSource::AssemblyKit => return Err(ApiError::InvalidParams("Assembly Kit tables can't be extracted.".to_owned()).into()),
1033        };
1034
1035        let extracted = self.extract_files(pack_key, &BTreeMap::from([(data_source, paths)]), &request.destination, request.as_tsv, options)?;
1036        Ok(FilesExtracted { extracted })
1037    }
1038
1039    /// Returns the contents of a file of any source.
1040    ///
1041    /// # Arguments
1042    ///
1043    /// * `request` - The file, and how to return it.
1044    /// * `enable_esf_editor` - If ESF files are decoded.
1045    /// * `disable_uuid_regeneration` - If tables keep their GUID when encoded to return their bytes.
1046    ///
1047    /// # Errors
1048    ///
1049    /// Fails if the file doesn't exist, or can't be returned in the requested format.
1050    pub fn read_file(&mut self, request: &ReadFile, enable_esf_editor: bool, disable_uuid_regeneration: bool) -> Result<FileContents> {
1051        self.check_file_exists(&request.file)?;
1052
1053        if request.format == ReadFormat::Raw {
1054            let compression_format = match request.file.source {
1055                FileSource::Pack(ref pack_key) => pack(&self.packs, pack_key)?.compression_format(),
1056                _ => CompressionFormat::None,
1057            };
1058
1059            let extra_data = encode_extra_data(&self.game, compression_format, disable_uuid_regeneration);
1060            let file = writable_file(&mut self.packs, &mut self.dependencies, &request.file)?;
1061            file.load()?;
1062
1063            let file_type = file.file_type();
1064            let bytes = match file.cached() {
1065                Ok(data) => data.to_vec(),
1066                Err(_) => file.encode(&extra_data, false, false, true)?
1067                    .ok_or_else(|| anyhow!("The file {} returned no data when encoded.", request.file.path))?,
1068            };
1069
1070            return Ok(FileContents { file_type, contents: FileData::Raw { base64: STANDARD.encode(bytes) } });
1071        }
1072
1073        let (pack_key, data_source) = pack_key_and_data_source(&request.file.source);
1074        let pack_key = pack_key.to_owned();
1075        let (decoded, file_type) = match self.decode_file(&pack_key, &request.file.path, data_source, enable_esf_editor)? {
1076            DecodedFile::Decoded(decoded, info) => (*decoded, *info.file_type()),
1077            DecodedFile::Notes(notes) => (RFileDecoded::Text(notes), FileType::Text),
1078            DecodedFile::Unsupported | DecodedFile::External => {
1079                return Err(ApiError::InvalidParams(format!("The file {} can't be decoded. Read it as raw instead.", request.file.path)).into());
1080            }
1081        };
1082
1083        let contents = match request.format {
1084            ReadFormat::Text => match decoded {
1085                RFileDecoded::Text(text) | RFileDecoded::VMD(text) | RFileDecoded::WSModel(text) => FileData::Text { text: text.contents().to_owned() },
1086                _ => return Err(ApiError::InvalidParams(format!("The file {} is not a text file.", request.file.path)).into()),
1087            },
1088            ReadFormat::Decoded | ReadFormat::Raw => FileData::Decoded { data: serde_json::to_value(decoded)? },
1089        };
1090
1091        Ok(FileContents { file_type, contents })
1092    }
1093
1094    /// Replaces the contents of a file of an open pack.
1095    ///
1096    /// With raw contents, the file is created if it doesn't exist.
1097    ///
1098    /// # Errors
1099    ///
1100    /// Fails if the file doesn't exist (except for raw contents), or if the contents don't fit the file.
1101    pub fn write_file(&mut self, request: &WriteFile) -> Result<()> {
1102        let pack = pack_mut(&mut self.packs, &request.pack)?;
1103        match request.contents {
1104            FileData::Raw { ref base64 } => {
1105                let bytes = STANDARD.decode(base64).map_err(|error| ApiError::InvalidParams(format!("Invalid base64: {error}")))?;
1106                let file_type = pack.files().get(&request.path).map_or(FileType::Unknown, |file| file.file_type());
1107                let mut file = RFile::new_from_vec(&bytes, file_type, 0, &request.path);
1108                if file_type == FileType::Unknown {
1109                    let _ = file.guess_file_type();
1110                }
1111
1112                pack.insert(file)?;
1113            }
1114            FileData::Text { ref text } => {
1115                let file = pack.files_mut().get_mut(&request.path).ok_or_else(|| ApiError::FileNotFound(request.path.clone()))?;
1116                let _ = file.decode(&Some(DecodeableExtraData::default()), true, false);
1117                match file.decoded_mut() {
1118                    Ok(RFileDecoded::Text(data)) | Ok(RFileDecoded::VMD(data)) | Ok(RFileDecoded::WSModel(data)) => { data.set_contents(text.clone()); }
1119                    _ => return Err(ApiError::InvalidParams(format!("The file {} is not a text file.", request.path)).into()),
1120                }
1121            }
1122            FileData::Decoded { ref data } => {
1123                if !pack.has_file(&request.path) && request.path != RESERVED_NAME_NOTES {
1124                    return Err(ApiError::FileNotFound(request.path.clone()).into());
1125                }
1126
1127                let decoded = serde_json::from_value::<RFileDecoded>(data.clone())
1128                    .map_err(|error| ApiError::InvalidParams(format!("Invalid decoded file: {error}")))?;
1129                self.save_file_from_view(&request.pack, &request.path, decoded)?;
1130            }
1131        }
1132
1133        Ok(())
1134    }
1135
1136    /// Returns the files inside an AnimPack of any source, sorted by path.
1137    pub fn list_animpack(&mut self, file: &FileRef) -> Result<FileList> {
1138        let (pack_key, data_source) = pack_key_and_data_source(&file.source);
1139        let pack_key = pack_key.to_owned();
1140        let files = match self.decode_file(&pack_key, &file.path, data_source, false)? {
1141            DecodedFile::Decoded(decoded, _) => match *decoded {
1142                RFileDecoded::AnimPack(anim_pack) => {
1143                    let mut files = anim_pack.files().values()
1144                        .map(|file| FileEntry { path: file.path_in_container_raw().to_owned(), file_type: file.file_type() })
1145                        .collect::<Vec<_>>();
1146                    files.sort_unstable_by(|a, b| a.path.cmp(&b.path));
1147                    files
1148                }
1149                _ => return Err(ApiError::InvalidParams(format!("The file {} is not an AnimPack.", file.path)).into()),
1150            },
1151            _ => return Err(ApiError::InvalidParams(format!("The file {} is not an AnimPack.", file.path)).into()),
1152        };
1153
1154        Ok(FileList { total: files.len(), files, folders: vec![] })
1155    }
1156
1157    /// Copies files of an open pack into an AnimPack of an open pack.
1158    pub fn add_to_animpack(&mut self, request: &AddToAnimPack) -> Result<FilesAdded> {
1159        let paths = self.pack_container_paths(&request.source_pack, &request.paths)?;
1160        let (added, not_added, error) = self.add_files_to_animpack(&request.source_pack, &request.pack, &request.animpack, &paths)?;
1161        Ok(FilesAdded { added: raw_paths(&added), not_added, error })
1162    }
1163
1164    /// Copies files of an AnimPack of any source into an open pack.
1165    pub fn extract_from_animpack(&mut self, request: &ExtractFromAnimPack) -> Result<FilesAdded> {
1166        let paths = self.animpack_container_paths(&request.file, &request.paths)?;
1167        let (pack_key, data_source) = pack_key_and_data_source(&request.file.source);
1168        let pack_key = pack_key.to_owned();
1169        let added = self.add_files_from_animpack(&pack_key, &request.pack, data_source, &request.file.path, &paths)?;
1170        Ok(FilesAdded { added: raw_paths(&added), ..FilesAdded::default() })
1171    }
1172
1173    /// Deletes files from an AnimPack of an open pack.
1174    pub fn delete_in_animpack(&mut self, request: &DeleteFromAnimPack) -> Result<()> {
1175        let file = FileRef { source: FileSource::Pack(request.pack.clone()), path: request.animpack.clone() };
1176        let paths = self.animpack_container_paths(&file, &request.paths)?;
1177        self.delete_from_animpack(&request.pack, &request.animpack, &paths)
1178    }
1179
1180    /// Resolves paths inside an AnimPack into file or folder paths, depending on if there's a file at each one.
1181    fn animpack_container_paths(&mut self, file: &FileRef, paths: &[String]) -> Result<Vec<ContainerPath>> {
1182        let files = self.list_animpack(file)?.files.into_iter().map(|file| file.path).collect::<HashSet<_>>();
1183        Ok(paths.iter().map(|path| container_path(|path| files.contains(path), path)).collect())
1184    }
1185
1186    /// Resolves paths of an open pack into file or folder paths, depending on if there's a file at each one.
1187    /// Checks that a file exists in its source.
1188    ///
1189    /// # Errors
1190    ///
1191    /// Fails if the pack of the file isn't open, or if the file doesn't exist. If its path is a folder of the source,
1192    /// the error says so, with the files in it.
1193    pub(super) fn check_file_exists(&self, file: &FileRef) -> Result<()> {
1194        let exists = match file.source {
1195            FileSource::Pack(ref pack_key) => pack(&self.packs, pack_key)?.files().contains_key(&file.path),
1196            FileSource::GameFiles => self.dependencies.file(&file.path, true, false, false).is_ok(),
1197            FileSource::ParentFiles => self.dependencies.file(&file.path, false, true, false).is_ok(),
1198            FileSource::AssemblyKit => file.path.split('/').nth(1)
1199                .is_some_and(|table_name| file.path == format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}") && self.dependencies.asskit_only_db_tables().contains_key(table_name)),
1200        };
1201
1202        if exists {
1203            return Ok(());
1204        }
1205
1206        let folder = ListFiles {
1207            source: file.source.clone(),
1208            path_prefix: format!("{}/", file.path.trim_end_matches('/')),
1209            recursive: true,
1210            file_types: None,
1211            offset: 0,
1212            limit: Some(FOLDER_FILES_IN_ERRORS),
1213        };
1214
1215        match self.list_files(&folder) {
1216            Ok(list) if list.total > 0 => {
1217                let paths = list.files.iter().map(|entry| entry.path.as_str()).collect::<Vec<_>>().join(", ");
1218                let more = if list.total > list.files.len() { format!(", and {} more", list.total - list.files.len()) } else { String::new() };
1219                Err(ApiError::InvalidParams(format!("{} is a folder, not a file. Its files are: {paths}{more}.", file.path)).into())
1220            }
1221            _ => Err(ApiError::FileNotFound(file.path.clone()).into()),
1222        }
1223    }
1224
1225    pub(super) fn pack_container_paths(&self, pack_key: &str, paths: &[String]) -> Result<Vec<ContainerPath>> {
1226        let pack = pack(&self.packs, pack_key)?;
1227        Ok(paths.iter().map(|path| container_path(|path| pack.has_file(path), path)).collect())
1228    }
1229}
1230
1231/// Returns a file of an open pack, the game files or the parent packs, mutably.
1232///
1233/// # Errors
1234///
1235/// Fails if the file doesn't exist, or if it's an Assembly Kit table, which isn't stored as a file.
1236fn writable_file<'a>(packs: &'a mut BTreeMap<String, Pack>, dependencies: &'a mut Dependencies, file: &FileRef) -> Result<&'a mut RFile> {
1237    let not_found = || ApiError::FileNotFound(file.path.clone());
1238    match file.source {
1239        FileSource::Pack(ref pack_key) => Ok(pack_mut(packs, pack_key)?.files_mut().get_mut(&file.path).ok_or_else(not_found)?),
1240        FileSource::GameFiles => dependencies.file_mut(&file.path, true, false).map_err(|_| not_found().into()),
1241        FileSource::ParentFiles => dependencies.file_mut(&file.path, false, true).map_err(|_| not_found().into()),
1242        FileSource::AssemblyKit => Err(ApiError::InvalidParams("Assembly Kit tables aren't files. Read them with table.rows.".to_owned()).into()),
1243    }
1244}
1245
1246/// Returns a path as a file path if `has_file` says there's a file at it, or as a folder path otherwise.
1247pub(super) fn container_path(has_file: impl Fn(&str) -> bool, path: &str) -> ContainerPath {
1248    if has_file(path) {
1249        ContainerPath::File(path.to_owned())
1250    } else {
1251        ContainerPath::Folder(path.trim_end_matches('/').to_owned())
1252    }
1253}
1254
1255/// Resolves paths into container paths, splitting out the ones that match no file.
1256///
1257/// # Arguments
1258///
1259/// * `paths` - Paths to resolve.
1260/// * `resolve` - Turns a path into a file or folder path.
1261/// * `exists` - Returns if a container path matches any file.
1262///
1263/// # Returns
1264///
1265/// The container paths matching files, and the paths matching none.
1266fn split_found(paths: &[String], resolve: impl Fn(&str) -> ContainerPath, exists: impl Fn(&ContainerPath) -> bool) -> (Vec<ContainerPath>, Vec<String>) {
1267    let mut found = Vec::with_capacity(paths.len());
1268    let mut not_found = vec![];
1269    for path in paths {
1270        let container_path = resolve(path);
1271        if exists(&container_path) {
1272            found.push(container_path);
1273        } else {
1274            not_found.push(path.to_owned());
1275        }
1276    }
1277
1278    (found, not_found)
1279}
1280
1281/// Returns the key of the pack of a file source (empty for other sources), and its data source.
1282pub(super) fn pack_key_and_data_source(source: &FileSource) -> (&str, DataSource) {
1283    let pack_key = match source {
1284        FileSource::Pack(pack_key) => pack_key.as_str(),
1285        _ => "",
1286    };
1287
1288    (pack_key, DataSource::from(source))
1289}
1290
1291/// Returns the raw paths of container paths.
1292pub(super) fn raw_paths(paths: &[ContainerPath]) -> Vec<String> {
1293    paths.iter().map(|path| path.path_raw().to_owned()).collect()
1294}
1295
1296/// Decodes a file, skipping the types the server doesn't decode.
1297fn decode(file: &mut RFile, game: &GameInfo, schema: &Option<Schema>, enable_esf_editor: bool) -> Result<DecodedFile> {
1298    let file_type = file.file_type();
1299    if UNDECODEABLE_FILE_TYPES.contains(&file_type) || (file_type == FileType::ESF && !enable_esf_editor) {
1300        return Ok(DecodedFile::Unsupported);
1301    }
1302
1303    let mut extra_data = DecodeableExtraData::default();
1304    extra_data.set_schema(schema.as_ref());
1305    extra_data.set_game_info(Some(game));
1306
1307    let decoded = file.decode(&Some(extra_data), true, true)?
1308        .ok_or_else(|| anyhow!("The file {} returned no data when decoded.", file.path_in_container_raw()))?;
1309
1310    Ok(DecodedFile::Decoded(Box::new(decoded), RFileInfo::from(&*file)))
1311}
1312
1313/// Decodes an AnimPack file, returning its decoded data.
1314fn decoded_anim_pack<'a>(file: &'a mut RFile, anim_pack_path: &str) -> Result<&'a mut AnimPack> {
1315    let _ = file.decode(&Some(DecodeableExtraData::default()), true, false);
1316    match file.decoded_mut() {
1317        Ok(RFileDecoded::AnimPack(anim_pack)) => Ok(anim_pack),
1318        Ok(decoded) => Err(anyhow!("We expected {} to be of type {} but found {}. This is either a bug or you did weird things with the game selected.", anim_pack_path, FileType::AnimPack, FileType::from(&*decoded))),
1319        Err(_) => Err(anyhow!("Failed to decode the file at the following path: {}", anim_pack_path)),
1320    }
1321}
1322
1323/// Returns copies of the files of a pack at the provided paths, with their data loaded in memory.
1324///
1325/// Files that fail to load are not copied, as unloaded copies would fail once the source pack changes on disk.
1326/// Their paths are returned after the copies, with the last error found loading them.
1327fn loaded_files_by_paths(pack: &Pack, paths: &[ContainerPath]) -> (Vec<RFile>, Vec<String>, Option<String>) {
1328    let mut files = vec![];
1329    let mut not_loaded = vec![];
1330    let mut last_error = None;
1331    for file in pack.files_by_paths(paths, false) {
1332        let mut file = file.clone();
1333        match file.load() {
1334            Ok(()) => files.push(file),
1335            Err(error) => {
1336                last_error = Some(format!("{}: {error}", file.path_in_container_raw()));
1337                not_loaded.push(file.path_in_container_raw().to_owned());
1338            }
1339        }
1340    }
1341
1342    (files, not_loaded, last_error)
1343}
1344
1345/// Inserts files into a pack, returning the paths of the ones inserted.
1346fn insert_files(pack: &mut Pack, files: Vec<RFile>) -> Vec<ContainerPath> {
1347    files.into_iter()
1348        .filter_map(|file| pack.insert(file).ok().flatten())
1349        .collect()
1350}
1351
1352/// Returns a path for a copy of a file, incrementing the trailing number of its name until it's free.
1353///
1354/// For example, "name.ext" becomes "name1.ext", and "name1.ext" becomes "name2.ext".
1355fn duplicate_path(pack: &Pack, path: &str) -> String {
1356    let (base, ext) = match path.rfind('.') {
1357        Some(dot_pos) => path.split_at(dot_pos),
1358        None => (path, ""),
1359    };
1360
1361    let base_trimmed = base.trim_end_matches(|c: char| c.is_ascii_digit());
1362    let first_counter = base[base_trimmed.len()..].parse::<u32>().unwrap_or(0) + 1;
1363
1364    (first_counter..)
1365        .map(|counter| format!("{}{}{}", base_trimmed, counter, ext))
1366        .find(|candidate| !pack.has_file(candidate))
1367        .expect("an unbounded range always finds a free name")
1368}
1369
1370/// Expands the selected paths of a pack into entries to paste, one per file.
1371///
1372/// - For a selected file `a/b/c`, the base path is `a/b` (parent folder), so pasting gives just `c`.
1373/// - For a selected folder `a/b`, the base path is `a` (parent of folder), so pasting preserves `b/...`.
1374fn paste_entries_from_paths(pack: &Pack, paths: &[ContainerPath], pack_key: &str) -> Vec<PasteEntry> {
1375    paths.iter()
1376        .flat_map(|path| {
1377            let base_path = path.path_raw().rfind('/')
1378                .map(|pos| path.path_raw()[..pos].to_string())
1379                .unwrap_or_default();
1380
1381            pack.files_by_paths(from_ref(path), false)
1382                .into_iter()
1383                .map(move |file| PasteEntry {
1384                    file_path: file.path_in_container_raw().to_string(),
1385                    base_path: base_path.clone(),
1386                    source_pack_key: pack_key.to_string(),
1387                })
1388        })
1389        .collect()
1390}
1391
1392/// Imports a table from the Assembly Kit as a new file, or `None` if it can't be imported.
1393///
1394/// CEO tables go under a `ceo_` prefixed folder.
1395fn import_asskit_table(dependencies: &Dependencies, schema: &Schema, table_name: &str, path: &str) -> Option<RFile> {
1396    let table = dependencies.import_from_ak(table_name, schema).ok()?;
1397    let path = if table_name.starts_with("ceo") {
1398        format!("ceo_{path}")
1399    } else {
1400        path.to_owned()
1401    };
1402
1403    Some(RFile::new_from_decoded(&RFileDecoded::DB(table), 0, &path))
1404}
1405
1406//-------------------------------------------------------------------------------//
1407//                                   Tests
1408//-------------------------------------------------------------------------------//
1409
1410#[cfg(test)]
1411#[path = "files_test.rs"]
1412mod files_test;