1use 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
47const 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
60pub type PathsByPack = BTreeMap<String, Vec<ContainerPath>>;
62
63#[derive(Debug)]
65pub enum DecodedFile {
66
67 Decoded(Box<RFileDecoded>, RFileInfo),
69
70 Notes(Text),
72
73 Unsupported,
75
76 External,
78}
79
80const FOLDER_FILES_IN_ERRORS: usize = 10;
82
83impl SessionState {
84
85 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1231fn 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
1246pub(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
1255fn 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
1281pub(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
1291pub(super) fn raw_paths(paths: &[ContainerPath]) -> Vec<String> {
1293 paths.iter().map(|path| path.path_raw().to_owned()).collect()
1294}
1295
1296fn 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
1313fn 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
1323fn 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
1345fn 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
1352fn 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
1370fn 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
1392fn 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#[cfg(test)]
1411#[path = "files_test.rs"]
1412mod files_test;