1use anyhow::{anyhow, Result};
31
32use itertools::Itertools;
33use open::that;
34use rayon::prelude::*;
35
36use std::collections::{BTreeMap, HashMap, HashSet};
37use std::env::temp_dir;
38use std::fs::{DirBuilder, File};
39use std::io::{BufWriter, Cursor, Write};
40use std::path::PathBuf;
41use std::slice::from_ref;
42use std::sync::{Arc, RwLock};
43use std::thread;
44use std::time::SystemTime;
45
46use rpfm_extensions::dependencies::*;
47use rpfm_extensions::diagnostics::Diagnostics;
48use rpfm_extensions::gltf::{gltf_from_rigid, save_gltf_to_disk};
49use rpfm_extensions::merge::{db_baseline, delta_merge_db, delta_merge_loc, loc_baseline, MergeConflict, MergeResolution};
50use rpfm_extensions::optimizer::OptimizableContainer;
51use rpfm_extensions::translator::PackTranslation;
52
53use rpfm_ipc::helpers::*;
54use rpfm_ipc::messages::OperationalMode;
55use rpfm_ipc::settings_keys::*;
56
57use rpfm_lib::compression::CompressionFormat;
58use rpfm_lib::files::{animpack::AnimPack, Container, ContainerPath, db::DB, DecodeableExtraData, EncodeableExtraData, FileType, loc::Loc, pack::*, portrait_settings::PortraitSettings, RFile, RFileDecoded, table::{DecodedData, Table}, text::*};
59use rpfm_lib::games::{GameInfo, LUA_REPO, LUA_BRANCH, LUA_REMOTE, OLD_AK_REPO, OLD_AK_BRANCH, OLD_AK_REMOTE, pfh_file_type::PFHFileType, supported_games::*, VanillaDBTableNameLogic};
60use rpfm_lib::games::{TRANSLATIONS_REPO, TRANSLATIONS_BRANCH, TRANSLATIONS_REMOTE};
61use rpfm_lib::integrations::{assembly_kit::*, git::*};
62use rpfm_lib::schema::*;
63use rpfm_lib::utils::*;
64
65use rpfm_telemetry::*;
66
67use crate::*;
68use crate::ceo_builder::{build_ceo_entries, build_ceo_post, get_trait_ceos};
69use crate::comms::CentralCommand;
70use crate::session::Session;
71use crate::settings::*;
72use crate::updater;
73
74use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
75
76pub const VANILLA_LOC_NAME: &str = "vanilla_english.tsv";
86
87pub const VANILLA_FIXES_NAME: &str = "vanilla_fixes_";
97
98const DEFAULT_PACK_STEM: &str = "new_pack";
101
102const DEFAULT_PACK_EXT: &str = ".pack";
105
106enum DeltaMergeOutcome {
108 Merged(RFile),
109 Conflicts(Vec<MergeConflict>),
110}
111
112fn command_name(cmd: &Command) -> String {
117 struct NameOnly {
118 out: String,
119 done: bool,
120 }
121
122 impl std::fmt::Write for NameOnly {
123 fn write_str(&mut self, s: &str) -> std::fmt::Result {
124 if self.done {
125 return Ok(());
126 }
127 for c in s.chars() {
128 if c.is_alphanumeric() || c == '_' {
129 self.out.push(c);
130 } else {
131 self.done = true;
132 return Ok(());
133 }
134 }
135 Ok(())
136 }
137 }
138
139 let mut capture = NameOnly { out: String::new(), done: false };
140 let _ = std::fmt::write(&mut capture, format_args!("{:?}", cmd));
141 capture.out
142}
143
144fn derive_new_pack_name(existing_keys: &BTreeMap<String, Pack>) -> String {
147 let base = format!("{}{}", DEFAULT_PACK_STEM, DEFAULT_PACK_EXT);
148 if !existing_keys.contains_key(&base) {
149 return base;
150 }
151
152 let mut suffix = 2;
153 loop {
154 let candidate = format!("{}_{}{}", DEFAULT_PACK_STEM, suffix, DEFAULT_PACK_EXT);
155 if !existing_keys.contains_key(&candidate) {
156 return candidate;
157 }
158 suffix += 1;
159 }
160}
161
162fn pack_key_from_path(path: &std::path::Path) -> String {
164 path.to_string_lossy().to_string()
165}
166
167fn unique_pack_key(key: &str, packs: &BTreeMap<String, Pack>) -> String {
169 if !packs.contains_key(key) {
170 return key.to_string();
171 }
172
173 let path = std::path::Path::new(key);
174 let parent = path.parent().map(|parent| parent.to_path_buf()).unwrap_or_default();
175 let stem = path.file_stem().map(|stem| stem.to_string_lossy().to_string()).unwrap_or_else(|| key.to_string());
176 let ext = path.extension().map(|ext| ext.to_string_lossy().to_string());
177
178 let mut suffix = 2;
179 loop {
180 let candidate_name = match &ext {
181 Some(ext) => format!("{stem} ({suffix}).{ext}"),
182 None => format!("{stem} ({suffix})"),
183 };
184 let candidate = parent.join(candidate_name).to_string_lossy().to_string();
185 if !packs.contains_key(&candidate) {
186 return candidate;
187 }
188 suffix += 1;
189 }
190}
191
192fn clipboard_entries_from_paths(pack: &Pack, paths: &[ContainerPath], pack_key: &str) -> Vec<(String, String, String)> {
200 let mut result = Vec::new();
201 for path in paths {
202 let base_path = match path.path_raw().rfind('/') {
203 Some(pos) => path.path_raw()[..pos].to_string(),
204 None => String::new(),
205 };
206 for file in pack.files_by_paths(from_ref(path), false) {
207 result.push((file.path_in_container_raw().to_string(), base_path.clone(), pack_key.to_string()));
208 }
209 }
210 result
211}
212
213fn get_pack<'a>(packs: &'a BTreeMap<String, Pack>, pack_key: &str, sender: &UnboundedSender<Response>) -> Option<&'a Pack> {
215 match packs.get(pack_key) {
216 Some(pack) => Some(pack),
217 None => {
218 CentralCommand::send_back(sender, Response::Error(format!("Pack not found: {}", pack_key)));
219 None
220 }
221 }
222}
223
224pub async fn background_loop(mut receiver: UnboundedReceiver<(UnboundedSender<Response>, Command)>, session: Arc<Session>) {
239
240 let supported_games = SupportedGames::default();
245 let mut game = supported_games.game(KEY_WARHAMMER_3).unwrap();
246 let mut schema = None;
247 let mut first_game_change_done = false;
248
249 let mut packs: BTreeMap<String, Pack> = BTreeMap::new();
251
252 let mut pack_modes: BTreeMap<String, OperationalMode> = BTreeMap::new();
254
255 let mut clipboard_entries: Vec<(String, String, String)> = Vec::new(); let mut clipboard_is_cut: bool = false;
258
259 let mut dependencies = Arc::new(RwLock::new(Dependencies::default()));
261
262 let _ = init_config_path();
264 let mut settings = Settings::init(false).unwrap_or_else(|error| {
265 rpfm_telemetry::warn!("Failed to initialize settings, falling back to defaults. Error: {error}");
266 Settings::default()
267 });
268 let mut backup_settings = settings.clone();
269
270 rpfm_telemetry::set_usage_telemetry_enabled(settings.bool(ENABLE_USAGE_TELEMETRY));
272 rpfm_telemetry::set_crash_reports_enabled(settings.bool(ENABLE_CRASH_REPORTS));
273
274 info!("Background Thread looping around…");
281 'background_loop: while let Some((sender, response)) = receiver.recv().await {
282
283 match &response {
286 Command::Exit | Command::ClientDisconnecting => {}
287 cmd => rpfm_telemetry::record_action(&command_name(cmd)),
288 }
289
290 match response {
291
292 Command::Exit => break,
294
295 Command::ClientDisconnecting => {
298 CentralCommand::send_back(&sender, Response::Success);
299 }
300
301 Command::CheckUpdates => {
303 let sender = sender.clone();
304 let settings = settings.clone();
305 tokio::spawn(async move {
306 let result = tokio::task::spawn_blocking(move || {
307 updater::check_updates_rpfm(&settings)
308 }).await.unwrap();
309
310 match result {
311 Ok(response) => CentralCommand::send_back(&sender, Response::APIResponse(response)),
312 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
313 }
314 });
315 }
316
317 Command::CheckSchemaUpdates => {
318 git_update_check(sender, schemas_path, SCHEMA_REPO, SCHEMA_BRANCH, SCHEMA_REMOTE);
319 }
320
321 Command::CheckLuaAutogenUpdates => {
322 git_update_check(sender, lua_autogen_base_path, LUA_REPO, LUA_BRANCH, LUA_REMOTE);
323 }
324
325 Command::CheckEmpireAndNapoleonAKUpdates => {
326 git_update_check(sender, old_ak_files_path, OLD_AK_REPO, OLD_AK_BRANCH, OLD_AK_REMOTE);
327 }
328
329 Command::CheckTranslationsUpdates => {
330 git_update_check(sender, translations_remote_path, TRANSLATIONS_REPO, TRANSLATIONS_BRANCH, TRANSLATIONS_REMOTE);
331 }
332
333 Command::ClosePack(pack_key) => {
335 if packs.remove(&pack_key).is_some() {
336 pack_modes.remove(&pack_key);
337 session.remove_pack_name(&pack_key);
338 CentralCommand::send_back(&sender, Response::Success);
339 } else {
340 CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key)));
341 }
342 }
343
344 Command::CloseAllPacks => {
345 for pack_key in packs.keys().cloned().collect::<Vec<_>>() {
346 session.remove_pack_name(&pack_key);
347 }
348 packs.clear();
349 pack_modes.clear();
350 CentralCommand::send_back(&sender, Response::Success);
351 }
352
353 Command::ListOpenPacks => {
355 let pack_list: Vec<(String, ContainerInfo)> = packs.iter()
356 .map(|(key, pack)| (key.clone(), ContainerInfo::from(pack)))
357 .collect();
358 CentralCommand::send_back(&sender, Response::VecStringContainerInfo(pack_list));
359 }
360
361 Command::NewPack => {
363 let pack_version = game.pfh_version_by_file_type(PFHFileType::Mod);
364 let key = derive_new_pack_name(&packs);
365 let mut pack = Pack::new_with_name_and_version(&key, pack_version);
366
367 if let Some(version_number) = game.game_version_number(&settings.path_buf(game.key())) {
368 pack.set_game_version(version_number);
369 }
370 session.add_pack_name(&key);
371 packs.insert(key.clone(), pack);
372 pack_modes.insert(key.clone(), OperationalMode::Normal);
373 CentralCommand::send_back(&sender, Response::String(key));
374 }
375
376 Command::OpenPackFiles(paths) => {
378 let key = if let Some(first_path) = paths.first() {
379 pack_key_from_path(first_path)
380 } else {
381 format!("{}{}", DEFAULT_PACK_STEM, DEFAULT_PACK_EXT)
382 };
383
384 let already_open = paths.first().is_some_and(|first_path| {
385 let normalized = first_path.to_string_lossy().replace('\\', "/");
386 packs.values().any(|pack| pack.disk_file_path() == normalized.as_str())
387 });
388
389 if already_open {
390 CentralCommand::send_back(&sender, Response::Error(format!(
391 "Pack '{}' is already open. Close it first if you want to reopen it.", key
392 )));
393 } else {
394 match Pack::read_and_merge(&paths, game, settings.bool("use_lazy_loading"), false, false) {
395 Ok(mut pack) => {
396
397 if let Some(ref schema) = schema {
399 let mut decode_extra_data = DecodeableExtraData::default();
400 decode_extra_data.set_schema(Some(schema));
401 let extra_data = Some(decode_extra_data);
402
403 let mut files = pack.files_by_type_mut(&[FileType::DB, FileType::Loc]);
404 files.par_iter_mut().for_each(|file| {
405 let _ = file.decode(&extra_data, true, false);
406 });
407 }
408
409 let key = unique_pack_key(&key, &packs);
410 session.add_pack_name(&key);
411
412 let info = ContainerInfo::from(&pack);
413 packs.insert(key.clone(), pack);
414 pack_modes.insert(key.clone(), OperationalMode::Normal);
415 CentralCommand::send_back(&sender, Response::StringContainerInfo(key, info));
416 }
417 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
418 }
419 }
420 }
421
422 Command::LoadAllCAPackFiles => {
424 let key = "CA PackFiles".to_string();
425
426 if packs.contains_key(&key) {
427 CentralCommand::send_back(&sender, Response::Error(format!(
428 "Pack '{}' is already open. Close it first if you want to reopen it.", key
429 )));
430 } else {
431 match Pack::read_and_merge_ca_packs(game, &settings.path_buf(game.key())) {
432 Ok(mut pack) => {
433
434 if let Some(ref schema) = schema {
436 let mut decode_extra_data = DecodeableExtraData::default();
437 decode_extra_data.set_schema(Some(schema));
438 let extra_data = Some(decode_extra_data);
439
440 let mut files = pack.files_by_type_mut(&[FileType::DB, FileType::Loc]);
441 files.par_iter_mut().for_each(|file| {
442 let _ = file.decode(&extra_data, true, false);
443 });
444 }
445
446 session.add_pack_name(&key);
447
448 let info = ContainerInfo::from(&pack);
449 packs.insert(key.clone(), pack);
450 pack_modes.insert(key.clone(), OperationalMode::Normal);
451 CentralCommand::send_back(&sender, Response::StringContainerInfo(key, info));
452 }
453 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
454 }
455 }
456 }
457
458 Command::SavePack(pack_key) => {
460 match packs.get_mut(&pack_key) {
461 Some(pack) => {
462 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
463
464 let pack_type = *pack.header().pfh_file_type();
465 if !settings.bool("allow_editing_of_ca_packfiles") && pack_type != PFHFileType::Mod && pack_type != PFHFileType::Movie {
466 CentralCommand::send_back(&sender, Response::Error(anyhow!("Pack cannot be saved due to being of CA-Only type. Either change the Pack Type or enable \"Allow Edition of CA Packs\" in the settings.").to_string()));
467 continue;
468 }
469
470 match pack.save(None, game, &extra_data) {
471 Ok(_) => CentralCommand::send_back(&sender, Response::ContainerInfo(From::from(&*pack))),
472 Err(error) => CentralCommand::send_back(&sender, Response::Error(anyhow!("Error while trying to save the currently open PackFile: {}", error).to_string())),
473 }
474 }
475 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
476 }
477 }
478
479 Command::SavePackAs(pack_key, path) => {
481 match packs.get_mut(&pack_key) {
482 Some(pack) => {
483 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
484
485 let pack_type = *pack.header().pfh_file_type();
486 if !settings.bool("allow_editing_of_ca_packfiles") && pack_type != PFHFileType::Mod && pack_type != PFHFileType::Movie {
487 CentralCommand::send_back(&sender, Response::Error(anyhow!("Pack cannot be saved due to being of CA-Only type. Either change the Pack Type or enable \"Allow Edition of CA Packs\" in the settings.").to_string()));
488 continue;
489 }
490
491 match pack.save(Some(&path), game, &extra_data) {
492 Ok(_) => CentralCommand::send_back(&sender, Response::ContainerInfo(From::from(&*pack))),
493 Err(error) => CentralCommand::send_back(&sender, Response::Error(anyhow!("Error while trying to save the currently open PackFile: {}", error).to_string())),
494 }
495 }
496 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
497 }
498 }
499
500 Command::CleanAndSavePackAs(pack_key, path) => {
502 match packs.get_mut(&pack_key) {
503 Some(pack) => {
504 pack.clean_undecoded();
505
506 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
507 match pack.save(Some(&path), game, &extra_data) {
508 Ok(_) => CentralCommand::send_back(&sender, Response::ContainerInfo(From::from(&*pack))),
509 Err(error) => CentralCommand::send_back(&sender, Response::Error(anyhow!("Error while trying to save the currently open PackFile: {}", error).to_string())),
510 }
511 }
512 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
513 }
514 }
515
516 Command::GetPackFileDataForTreeView(pack_key) => {
518 match packs.get(&pack_key) {
519 Some(pack) => {
520 CentralCommand::send_back(&sender, Response::ContainerInfoVecRFileInfo((
521 From::from(pack),
522 pack.files().par_iter().map(|(_, file)| From::from(file)).collect(),
523 )));
524 }
525 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
526 }
527 }
528
529 Command::GetRFileInfo(pack_key, path) => {
531 match packs.get(&pack_key) {
532 Some(pack) => {
533 CentralCommand::send_back(&sender, Response::OptionRFileInfo(
534 pack.files().get(&path).map(From::from)
535 ));
536 }
537 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
538 }
539 }
540
541 Command::GetPackedFilesInfo(pack_key, paths) => {
543 match packs.get(&pack_key) {
544 Some(pack) => {
545 let paths = paths.iter().map(|path| ContainerPath::File(path.to_owned())).collect::<Vec<_>>();
546 CentralCommand::send_back(&sender, Response::VecRFileInfo(
547 pack.files_by_paths(&paths, false).into_iter().map(From::from).collect()
548 ));
549 }
550 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
551 }
552 }
553
554 Command::GlobalSearch(_pack_key, mut global_search) => {
556 match schema {
557 Some(ref schema) => {
558 global_search.search(game, schema, &mut packs, &mut dependencies.write().unwrap(), &[]);
559 let packed_files_info = RFileInfo::info_from_global_search(&global_search, &packs);
560 CentralCommand::send_back(&sender, Response::GlobalSearchVecRFileInfo(Box::new(global_search), packed_files_info));
561 }
562 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("Schema not found. Maybe you need to download it?").to_string())),
563 }
564 }
565
566 Command::GetGameSelected => CentralCommand::send_back(&sender, Response::String(game.key().to_owned())),
567 Command::SetGameSelected(game_key, rebuild_dependencies) => {
568 info!("Setting game selected.");
569 let game_changed = game.key() != game_key || !first_game_change_done;
570 game = match supported_games.game(&game_key) {
571 Some(gi) => gi,
572 None => {
573 CentralCommand::send_back(&sender, Response::Error(anyhow!("The selected game is not supported!").to_string()));
574 continue;
575 }
576 };
577
578 for pack in packs.values_mut() {
580 let current_cf = pack.compression_format();
581 if current_cf != CompressionFormat::None && !game.compression_formats_supported().contains(¤t_cf) {
582 if let Some(new_cf) = game.compression_formats_supported().first() {
583 pack.set_compression_format(*new_cf, game);
584 } else {
585 pack.set_compression_format(CompressionFormat::None, game);
586 }
587 }
588 }
589
590 load_schema(&mut schema, &mut packs, game, &settings);
599
600 if rebuild_dependencies {
601 info!("Branch 1.");
602 let pack_dependencies: Vec<_> = packs.values()
604 .flat_map(|pack| pack.dependencies().iter().map(|x| x.1.clone()))
605 .collect();
606 let game_path = settings.path_buf(game.key());
608 let secondary_path = settings.path_buf(SECONDARY_PATH);
609 let game_clone = game.clone();
610 let handle = thread::spawn(move || {
611 let file_path = dependencies_cache_path().unwrap().join(game_clone.dependencies_cache_file_name());
612 let file_path = if game_changed { Some(&*file_path) } else { None };
613 let _ = dependencies.write().unwrap().rebuild(&None, &pack_dependencies, file_path, &game_clone, &game_path, &secondary_path);
614 dependencies
615 });
616
617 dependencies = handle.join().unwrap();
619 let dependencies_info = DependenciesInfo::new(&dependencies.read().unwrap(), game.vanilla_db_table_name_logic());
620 info!("Sending dependencies info after game selected change.");
621 let cf = packs.values().next().map(|p| p.compression_format()).unwrap_or(CompressionFormat::None);
623 CentralCommand::send_back(&sender, Response::CompressionFormatDependenciesInfo(cf, Some(dependencies_info)));
624
625 dependencies.write().unwrap().decode_tables(&schema);
627 }
628
629 else {
631 info!("Branch 2.");
632 let cf = packs.values().next().map(|p| p.compression_format()).unwrap_or(CompressionFormat::None);
633 CentralCommand::send_back(&sender, Response::CompressionFormatDependenciesInfo(cf, None));
634 };
635
636 for pack in packs.values_mut() {
638 if !pack.disk_file_path().is_empty() {
639 let pfh_file_type = *pack.header().pfh_file_type();
640 pack.header_mut().set_pfh_version(game.pfh_version_by_file_type(pfh_file_type));
641
642 if let Some(version_number) = game.game_version_number(&settings.path_buf(game.key())) {
643 pack.set_game_version(version_number);
644 }
645 }
646 }
647
648 if !first_game_change_done {
649 first_game_change_done = true;
650 }
651
652 info!("Switching game selected done.");
653 }
654
655 Command::GenerateDependenciesCache => {
657 let game_path = settings.path_buf(game.key());
658 let ignore_game_files_in_ak = settings.bool("ignore_game_files_in_ak");
659 let asskit_path = settings.assembly_kit_path(game).ok();
660
661 if game_path.is_dir() {
662 match Dependencies::generate_dependencies_cache(&schema, game, &game_path, &asskit_path, ignore_game_files_in_ak) {
663 Ok(mut cache) => {
664 let dependencies_path = dependencies_cache_path().unwrap().join(game.dependencies_cache_file_name());
665 match cache.save(&dependencies_path) {
666 Ok(_) => {
667 let secondary_path = settings.path_buf(SECONDARY_PATH);
668 let pack_dependencies: Vec<_> = packs.values()
669 .flat_map(|pack| pack.dependencies().iter().map(|x| x.1.clone()))
670 .collect();
671 let _ = dependencies.write().unwrap().rebuild(&schema, &pack_dependencies, Some(&dependencies_path), game, &game_path, &secondary_path);
672 let dependencies_info = DependenciesInfo::new(&dependencies.read().unwrap(), game.vanilla_db_table_name_logic());
673 CentralCommand::send_back(&sender, Response::DependenciesInfo(dependencies_info));
674 },
675 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
676 }
677 }
678 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
679 }
680 } else {
681 CentralCommand::send_back(&sender, Response::Error(anyhow!("Game Path not configured. Go to <i>'PackFile/Settings'</i> and configure it.").to_string()));
682 }
683 }
684
685 Command::UpdateCurrentSchemaFromAssKit => {
687 let ignore_game_files_in_ak = settings.bool("ignore_game_files_in_ak");
688
689 if let Some(ref mut schema) = schema {
690 match settings.assembly_kit_path(game) {
691 Ok(asskit_path) => {
692 let schema_path = schemas_path().unwrap().join(game.schema_file_name());
693
694 let dependencies = dependencies.read().unwrap();
695 if let Ok(mut tables_to_check) = dependencies.db_and_loc_data(true, false, true, false) {
696
697 for pack in packs.values() {
699 if !pack.disk_file_path().is_empty() {
700 tables_to_check.append(&mut pack.files_by_type(&[FileType::DB]));
701 }
702 }
703
704 let mut tables_to_check_split: HashMap<String, Vec<DB>> = HashMap::new();
706 for table_to_check in tables_to_check {
707 if let Ok(RFileDecoded::DB(table)) = table_to_check.decoded() {
708 match tables_to_check_split.get_mut(table.table_name()) {
709 Some(tables) => {
710
711 match tables.iter_mut().find(|x| x.definition().version() == table.definition().version()) {
713 Some(db_source) => *db_source = DB::merge(&[db_source, table]).unwrap(),
714 None => tables.push((table.clone()).clone()),
715 }
716 }
717 None => {
718 tables_to_check_split.insert(table.table_name().to_owned(), vec![table.clone()]);
719 }
720 }
721 }
722 }
723
724 let tables_to_skip = if ignore_game_files_in_ak {
725 dependencies.vanilla_loose_tables().keys().chain(dependencies.vanilla_tables().keys()).map(|x| &**x).collect::<Vec<_>>()
726 } else {
727 vec![]
728 };
729
730 match update_schema_from_raw_files(schema, game, &asskit_path, &schema_path, &tables_to_skip, &tables_to_check_split) {
731 Ok(possible_loc_fields) => {
732
733 let local_packs = if packs.is_empty() { None } else { Some(&packs) };
737 if dependencies.bruteforce_loc_key_order(schema, possible_loc_fields, local_packs, None).is_ok() {
738
739 let _ = update_schema_from_raw_files(schema, game, &asskit_path, &schema_path, &tables_to_skip, &tables_to_check_split);
741
742 if dependencies.generate_automatic_patches(schema, &packs).is_ok() {
744
745 schema.definitions_mut().par_iter_mut().for_each(|x| {
747 x.1.iter_mut().for_each(|y| {
748 y.fields_mut().iter_mut().for_each(|z| {
749 if let Some(path) = z.filename_relative_path(None) {
750 if path.len() == 1 && path[0].contains(",") {
751 let new_paths = path[0].split(',').map(|x| x.trim()).join(";");
752 z.set_filename_relative_path(Some(new_paths));
753 }
754 }
755 });
756 });
757 });
758
759 match schema.save(&schemas_path().unwrap().join(game.schema_file_name())) {
760 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
761 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
762 }
763 } else {
764 CentralCommand::send_back(&sender, Response::Success)
765 }
766 } else {
767 CentralCommand::send_back(&sender, Response::Success)
768 }
769 },
770 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
771 }
772 }
773 }
774 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
775 }
776 } else {
777 CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string()));
778 }
779 }
780
781 Command::OptimizePackFile(pack_key, options) => {
783 match packs.get_mut(&pack_key) {
784 Some(pack) => {
785 if let Some(ref schema) = schema {
786 match pack.optimize(None, &mut dependencies.write().unwrap(), schema, game, &options) {
787 Ok((paths_to_delete, paths_to_add)) => CentralCommand::send_back(&sender, Response::HashSetStringHashSetString(paths_to_delete, paths_to_add)),
788 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
789 }
790 } else {
791 CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string()));
792 }
793 }
794 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
795 }
796 }
797
798 Command::PatchSiegeAI(pack_key) => {
800 match packs.get_mut(&pack_key) {
801 Some(pack) => {
802 match pack.patch_siege_ai() {
803 Ok(result) => CentralCommand::send_back(&sender, Response::StringVecContainerPath(result.0, result.1)),
804 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string()))
805 }
806 }
807 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
808 }
809 }
810
811 Command::SetPackFileType(pack_key, new_type) => {
813 match packs.get_mut(&pack_key) {
814 Some(pack) => {
815 pack.set_pfh_file_type(new_type);
816 CentralCommand::send_back(&sender, Response::Success);
817 }
818 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
819 }
820 }
821
822 Command::ChangeIndexIncludesTimestamp(pack_key, state) => {
824 match packs.get_mut(&pack_key) {
825 Some(pack) => {
826 let mut bitmask = pack.bitmask();
827 bitmask.set(PFHFlags::HAS_INDEX_WITH_TIMESTAMPS, state);
828 pack.set_bitmask(bitmask);
829 CentralCommand::send_back(&sender, Response::Success);
830 }
831 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
832 }
833 },
834
835 Command::ChangeCompressionFormat(pack_key, cf) => {
837 match packs.get_mut(&pack_key) {
838 Some(pack) => {
839 CentralCommand::send_back(&sender, Response::CompressionFormat(pack.set_compression_format(cf, game)));
840 }
841 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
842 }
843 },
844
845 Command::GetPackFilePath(pack_key) => {
847 match packs.get(&pack_key) {
848 Some(pack) => CentralCommand::send_back(&sender, Response::PathBuf(PathBuf::from(pack.disk_file_path()))),
849 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
850 }
851 },
852
853 Command::GetDependencyPackFilesList(pack_key) => {
855 match packs.get(&pack_key) {
856 Some(pack) => CentralCommand::send_back(&sender, Response::VecBoolString(pack.dependencies().to_vec())),
857 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
858 }
859 },
860
861 Command::SetDependencyPackFilesList(pack_key, dep_packs) => {
863 match packs.get_mut(&pack_key) {
864 Some(pack) => {
865 pack.set_dependencies(dep_packs);
866 CentralCommand::send_back(&sender, Response::Success);
867 }
868 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
869 }
870 },
871
872 Command::IsThereADependencyDatabase(include_asskit) => {
874 let are_dependencies_loaded = dependencies.read().unwrap().is_vanilla_data_loaded(include_asskit);
875 CentralCommand::send_back(&sender, Response::Bool(are_dependencies_loaded))
876 },
877
878 Command::NewPackedFile(pack_key, path, new_packed_file) => {
880 let decoded = match new_packed_file {
881 NewFile::AnimPack(_) => {
882 let file = AnimPack::default();
883 RFileDecoded::AnimPack(file)
884 },
885 NewFile::DB(_, table, version) => {
886 if let Some(ref schema) = schema {
887 match schema.definition_by_name_and_version(&table, version) {
888 Some(definition) => {
889 let patches = schema.patches_for_table(&table);
890 let file = DB::new(definition, patches, &table);
891 RFileDecoded::DB(file)
892 }
893 None => {
894 CentralCommand::send_back(&sender, Response::Error(format!("No definitions found for the table `{}`, version `{}` in the currently loaded schema.", table, version)));
895 continue;
896 }
897 }
898 } else {
899 CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string()));
900 continue;
901 }
902 },
903 NewFile::Loc(_) => {
904 let file = Loc::new();
905 RFileDecoded::Loc(file)
906 }
907 NewFile::PortraitSettings(_, version, entries) => {
908 let mut file = PortraitSettings::default();
909 file.set_version(version);
910
911 if !entries.is_empty() {
912
913 let mut dependencies = dependencies.write().unwrap();
914 let mut vanilla_files = dependencies.files_by_types_mut(&[FileType::PortraitSettings], true, true);
915 let vanilla_files_decoded = vanilla_files.iter_mut()
916 .filter_map(|(_, file)| file.decode(&None, false, true).ok().flatten())
917 .filter_map(|file| if let RFileDecoded::PortraitSettings(file) = file { Some(file) } else { None })
918 .collect::<Vec<_>>();
919
920 let vanilla_values = vanilla_files_decoded.iter()
921 .flat_map(|file| file.entries())
922 .map(|entry| (entry.id(), entry))
923 .collect::<HashMap<_,_>>();
924
925 for (from_id, to_id) in entries {
926 if let Some(from_entry) = vanilla_values.get(&from_id) {
927 let mut new_entry = (*from_entry).clone();
928 new_entry.set_id(to_id);
929 file.entries_mut().push(new_entry);
930 }
931 }
932 }
933
934 RFileDecoded::PortraitSettings(file)
935 },
936 NewFile::Text(_, text_type) => {
937 let mut file = Text::default();
938 file.set_format(text_type);
939 RFileDecoded::Text(file)
940 },
941
942 NewFile::VMD(_) => {
943 let mut file = Text::default();
944 file.set_format(TextFormat::Xml);
945 RFileDecoded::VMD(file)
946 },
947
948 NewFile::WSModel(_) => {
949 let mut file = Text::default();
950 file.set_format(TextFormat::Xml);
951 RFileDecoded::WSModel(file)
952 },
953 };
954 let file = RFile::new_from_decoded(&decoded, 0, &path);
955 match packs.get_mut(&pack_key) {
956 Some(pack) => {
957 match pack.insert(file) {
958 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
959 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
960 }
961 }
962 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
963 }
964 }
965
966 Command::AddPackedFiles(pack_key, source_paths, destination_paths, paths_to_ignore) => {
968 match packs.get_mut(&pack_key) {
969 Some(pack) => {
970 let mut added_paths = vec![];
971 let mut it_broke = None;
972
973 let paths = source_paths.iter().zip(destination_paths.iter()).collect::<Vec<(&PathBuf, &ContainerPath)>>();
974 for (source_path, destination_path) in paths {
975
976 if let Some(ref paths_to_ignore) = paths_to_ignore {
978 if paths_to_ignore.iter().any(|x| source_path.starts_with(x)) {
979 continue;
980 }
981 }
982
983 match destination_path {
984 ContainerPath::File(destination_path) => {
985 match pack.insert_file(source_path, destination_path, &schema) {
986 Ok(path) => if let Some(path) = path {
987 added_paths.push(path);
988 },
989 Err(error) => it_broke = Some(error),
990 }
991 },
992
993 ContainerPath::Folder(destination_path) => {
995 match pack.insert_folder(source_path, destination_path, &None, &schema, settings.bool("include_base_folder_on_add_from_folder")) {
996 Ok(mut paths) => added_paths.append(&mut paths),
997 Err(error) => it_broke = Some(error),
998 }
999 },
1000 }
1001 }
1002
1003 CentralCommand::send_back(&sender, Response::VecContainerPathOptionString(added_paths.to_vec(), it_broke.map(|e| e.to_string())));
1004
1005 if let Some(ref schema) = schema {
1007 let mut decode_extra_data = DecodeableExtraData::default();
1008 decode_extra_data.set_schema(Some(schema));
1009 let extra_data = Some(decode_extra_data);
1010
1011 let mut files = pack.files_by_paths_mut(&added_paths, false);
1012 files.par_iter_mut()
1013 .filter(|file| file.file_type() == FileType::DB || file.file_type() == FileType::Loc)
1014 .for_each(|file| {
1015 let _ = file.decode(&extra_data, true, false);
1016 }
1017 );
1018 }
1019 }
1020 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1021 }
1022 }
1023
1024 Command::AddPackedFilesFromPackFile(target_key, source_key, paths) => {
1026 let files = match packs.get(&source_key) {
1028 Some(source_pack) => {
1029 source_pack.files_by_paths(&paths, false)
1030 .into_iter()
1031 .map(|file| {
1032 let mut file = file.clone();
1033 let _ = file.load();
1034 file
1035 })
1036 .collect::<Vec<RFile>>()
1037 }
1038 None => {
1039 CentralCommand::send_back(&sender, Response::Error(format!("Source pack not found: {}", source_key)));
1040 continue;
1041 }
1042 };
1043
1044 match packs.get_mut(&target_key) {
1046 Some(target_pack) => {
1047 let mut added_paths = Vec::with_capacity(files.len());
1048 for file in files {
1049 if let Ok(Some(path)) = target_pack.insert(file) {
1050 added_paths.push(path);
1051 }
1052 }
1053
1054 CentralCommand::send_back(&sender, Response::VecContainerPath(added_paths.to_vec()));
1055
1056 if let Some(ref schema) = schema {
1058 let mut decode_extra_data = DecodeableExtraData::default();
1059 decode_extra_data.set_schema(Some(schema));
1060 let extra_data = Some(decode_extra_data);
1061
1062 let mut files = target_pack.files_by_paths_mut(&added_paths, false);
1063 files.par_iter_mut()
1064 .filter(|file| file.file_type() == FileType::DB || file.file_type() == FileType::Loc)
1065 .for_each(|file| {
1066 let _ = file.decode(&extra_data, true, false);
1067 }
1068 );
1069 }
1070 }
1071 None => CentralCommand::send_back(&sender, Response::Error(format!("Target pack not found: {}", target_key))),
1072 }
1073 }
1074
1075 Command::AddPackedFilesFromPackFileToAnimpack(source_pack_key, anim_pack_key, anim_pack_path, paths) => {
1077 let files = match packs.get(&source_pack_key) {
1078 Some(pack) => pack.files_by_paths(&paths, false)
1079 .into_iter()
1080 .map(|file| {
1081 let mut file = file.clone();
1082 let _ = file.load();
1083 file
1084 })
1085 .collect::<Vec<RFile>>(),
1086 None => {
1087 CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", source_pack_key)));
1088 continue;
1089 }
1090 };
1091
1092 match packs.get_mut(&anim_pack_key) {
1093 Some(pack) => {
1094 match pack.files_mut().get_mut(&anim_pack_path) {
1095 Some(file) => {
1096
1097 let extra_data = DecodeableExtraData::default();
1099 let _ = file.decode(&Some(extra_data), true, false);
1101
1102 match file.decoded_mut() {
1103 Ok(decoded) => match decoded {
1104 RFileDecoded::AnimPack(anim_pack) => {
1105 let mut paths = Vec::with_capacity(files.len());
1106 for file in files {
1107 if let Ok(Some(path)) = anim_pack.insert(file) {
1108 paths.push(path);
1109 }
1110 }
1111
1112 CentralCommand::send_back(&sender, Response::VecContainerPath(paths.to_vec()));
1113 }
1114 _ => CentralCommand::send_back(&sender, Response::Error(format!("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)))),
1115 }
1116 _ => CentralCommand::send_back(&sender, Response::Error(format!("Failed to decode the file at the following path: {}", anim_pack_path))),
1117 }
1118 }
1119 None => CentralCommand::send_back(&sender, Response::Error(format!("File not found in the Pack: {}.", anim_pack_path))),
1120 }
1121 }
1122 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", anim_pack_key))),
1123 }
1124 }
1125
1126 Command::AddPackedFilesFromAnimpack(anim_pack_key, dest_pack_key, data_source, anim_pack_path, paths) => {
1128 let mut dependencies = dependencies.write().unwrap();
1129 let anim_pack_file = match data_source {
1130 DataSource::PackFile => packs.get_mut(&anim_pack_key).and_then(|pack| pack.files_mut().get_mut(&anim_pack_path)),
1131 DataSource::GameFiles => dependencies.file_mut(&anim_pack_path, true, false).ok(),
1132 DataSource::ParentFiles => dependencies.file_mut(&anim_pack_path, false, true).ok(),
1133 DataSource::AssKitFiles |
1134 DataSource::ExternalFile => unreachable!("add_files_to_animpack"),
1135 };
1136
1137 let files = match anim_pack_file {
1138 Some(file) => {
1139
1140 let extra_data = DecodeableExtraData::default();
1142 let _ = file.decode(&Some(extra_data), true, false);
1144
1145 match file.decoded_mut() {
1146 Ok(decoded) => match decoded {
1147 RFileDecoded::AnimPack(anim_pack) => anim_pack.files_by_paths(&paths, false).into_iter().cloned().collect::<Vec<RFile>>(),
1148 _ => {
1149 CentralCommand::send_back(&sender, Response::Error(format!("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))));
1150 continue;
1151 },
1152 }
1153 _ => {
1154 CentralCommand::send_back(&sender, Response::Error(format!("Failed to decode the file at the following path: {}", anim_pack_path)));
1155 continue;
1156 },
1157 }
1158 }
1159 None => {
1160 CentralCommand::send_back(&sender, Response::Error(format!("The file with the path {} doesn't exists on the open Pack.", anim_pack_path)));
1161 continue;
1162 }
1163 };
1164
1165 let result_paths = files.iter().map(|file| file.path_in_container()).collect::<Vec<_>>();
1166 match packs.get_mut(&dest_pack_key) {
1167 Some(pack) => {
1168 for mut file in files {
1169 let _ = file.guess_file_type();
1170 let _ = pack.insert(file);
1171 }
1172 CentralCommand::send_back(&sender, Response::VecContainerPath(result_paths));
1173 }
1174 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", dest_pack_key))),
1175 }
1176 }
1177
1178 Command::DeleteFromAnimpack(pack_key, anim_pack_path, paths) => {
1180 match packs.get_mut(&pack_key) {
1181 Some(pack) => {
1182 match pack.files_mut().get_mut(&anim_pack_path) {
1183 Some(file) => {
1184
1185 let extra_data = DecodeableExtraData::default();
1187 let _ = file.decode(&Some(extra_data), true, false);
1189
1190 match file.decoded_mut() {
1191 Ok(decoded) => match decoded {
1192 RFileDecoded::AnimPack(anim_pack) => {
1193 for path in paths {
1194 anim_pack.remove(&path);
1195 }
1196
1197 CentralCommand::send_back(&sender, Response::Success);
1198 }
1199 _ => CentralCommand::send_back(&sender, Response::Error(format!("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)))),
1200 }
1201 _ => CentralCommand::send_back(&sender, Response::Error(format!("Failed to decode the file at the following path: {}", anim_pack_path))),
1202 }
1203 }
1204 None => CentralCommand::send_back(&sender, Response::Error(format!("File not found in the Pack: {}.", anim_pack_path))),
1205 }
1206 }
1207 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1208 }
1209 }
1210
1211 Command::DecodePackedFile(pack_key, path, data_source) => {
1213 info!("Trying to decode a file. Path: {}", &path);
1214 info!("Trying to decode a file. Data Source: {}", &data_source);
1215
1216 match data_source {
1217 DataSource::PackFile => {
1218 match packs.get_mut(&pack_key) {
1219 Some(pack) => {
1220 if path == RESERVED_NAME_NOTES {
1221 let mut note = Text::default();
1222 note.set_format(TextFormat::Markdown);
1223 note.set_contents(pack.notes().pack_notes().to_owned());
1224 CentralCommand::send_back(&sender, Response::Text(note));
1225 }
1226
1227 else {
1228
1229 match pack.files_mut().get_mut(&path) {
1231 Some(file) => decode_and_send_file(file, &sender, &settings, game, &schema),
1232 None => CentralCommand::send_back(&sender, Response::Error(format!("The file with the path {} hasn't been found on this Pack.", path))),
1233 }
1234 }
1235 }
1236 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1237 }
1238 }
1239
1240 DataSource::ParentFiles => {
1241 match dependencies.write().unwrap().file_mut(&path, false, true) {
1242 Ok(file) => decode_and_send_file(file, &sender, &settings, game, &schema),
1243 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1244 }
1245 }
1246
1247 DataSource::GameFiles => {
1248 match dependencies.write().unwrap().file_mut(&path, true, false) {
1249 Ok(file) => decode_and_send_file(file, &sender, &settings, game, &schema),
1250 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1251 }
1252 }
1253
1254 DataSource::AssKitFiles => {
1255 let path_split = path.split('/').collect::<Vec<_>>();
1256 if path_split.len() > 2 {
1257 match dependencies.read().unwrap().asskit_only_db_tables().get(path_split[1]) {
1258 Some(db) => CentralCommand::send_back(&sender, Response::DBRFileInfo(db.clone(), RFileInfo::default())),
1259 None => CentralCommand::send_back(&sender, Response::Error(format!("Table {} not found on Assembly Kit files.", path))),
1260 }
1261 } else {
1262 CentralCommand::send_back(&sender, Response::Error(format!("Path {} doesn't contain an identifiable table name.", path)));
1263 }
1264 }
1265
1266 DataSource::ExternalFile => {
1267 CentralCommand::send_back(&sender, Response::Success);
1268 }
1269 }
1270 }
1271
1272 Command::SavePackedFileFromView(pack_key, path, file_decoded) => {
1274 match packs.get_mut(&pack_key) {
1275 Some(pack) => {
1276 if path == RESERVED_NAME_NOTES {
1277 if let RFileDecoded::Text(data) = file_decoded {
1278 pack.notes_mut().set_pack_notes(data.contents().to_owned());
1279 }
1280 }
1281 else if let Some(file) = pack.files_mut().get_mut(&path) {
1282 if let Err(error) = file.set_decoded(file_decoded) {
1283 CentralCommand::send_back(&sender, Response::Error(error.to_string()));
1284 continue;
1285 }
1286 }
1287 CentralCommand::send_back(&sender, Response::Success);
1288 }
1289 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1290 }
1291 }
1292
1293 Command::DeletePackedFiles(pack_key, paths) => {
1295 match packs.get_mut(&pack_key) {
1296 Some(pack) => CentralCommand::send_back(&sender, Response::VecContainerPath(paths.iter().flat_map(|path| pack.remove(path)).collect())),
1297 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1298 }
1299 }
1300
1301 Command::CopyPackedFiles(paths_by_pack) => {
1303 clipboard_entries.clear();
1304 for (pack_key, paths) in &paths_by_pack {
1305 if let Some(pack) = packs.get(pack_key) {
1306 clipboard_entries.extend(clipboard_entries_from_paths(pack, paths, pack_key));
1307 }
1308 }
1309 clipboard_is_cut = false;
1310 CentralCommand::send_back(&sender, Response::Success);
1311 }
1312
1313 Command::CutPackedFiles(paths_by_pack) => {
1315 clipboard_entries.clear();
1316 for (pack_key, paths) in &paths_by_pack {
1317 if let Some(pack) = packs.get(pack_key) {
1318 clipboard_entries.extend(clipboard_entries_from_paths(pack, paths, pack_key));
1319 }
1320 }
1321 clipboard_is_cut = true;
1322 CentralCommand::send_back(&sender, Response::Success);
1323 }
1324
1325 Command::PastePackedFiles(target_key, destination_path) => {
1327 if clipboard_entries.is_empty() {
1328 CentralCommand::send_back(&sender, Response::Error("Clipboard is empty.".to_string()));
1329 } else {
1330
1331 let mut files_to_insert: Vec<RFile> = Vec::with_capacity(clipboard_entries.len());
1334 for (file_path, base_path, source_key) in &clipboard_entries {
1335 if let Some(source_pack) = packs.get(source_key) {
1336 let path_as_container = ContainerPath::File(file_path.clone());
1337 let found = source_pack.files_by_paths(&[path_as_container], false);
1338 if let Some(file) = found.first() {
1339 let mut new_file = (*file).clone();
1340 let _ = new_file.load();
1341
1342 let relative_path = if !base_path.is_empty() && file_path.starts_with(base_path) {
1344 file_path[base_path.len()..].trim_start_matches('/')
1345 } else {
1346 file_path
1347 };
1348 let new_path = if destination_path.is_empty() {
1349 relative_path.to_string()
1350 } else {
1351 format!("{}/{}", destination_path.trim_end_matches('/'), relative_path)
1352 };
1353 new_file.set_path_in_container_raw(&new_path);
1354 files_to_insert.push(new_file);
1355 }
1356 }
1357 }
1358
1359 let mut cut_deleted_by_pack: BTreeMap<String, Vec<ContainerPath>> = BTreeMap::new();
1361 if clipboard_is_cut {
1362 for (file_path, _, source_key) in &clipboard_entries {
1363 if let Some(source_pack) = packs.get_mut(source_key) {
1364 let removed = source_pack.remove(&ContainerPath::File(file_path.clone()));
1365 cut_deleted_by_pack.entry(source_key.clone()).or_default().extend(removed);
1366 }
1367 }
1368 }
1369
1370 match packs.get_mut(&target_key) {
1372 Some(target_pack) => {
1373 let mut added_paths = Vec::with_capacity(files_to_insert.len());
1374 for new_file in files_to_insert {
1375 if let Ok(Some(path)) = target_pack.insert(new_file) {
1376 added_paths.push(path);
1377 }
1378 }
1379
1380 if let Some(ref schema) = schema {
1382 let mut decode_extra_data = DecodeableExtraData::default();
1383 decode_extra_data.set_schema(Some(schema));
1384 let extra_data = Some(decode_extra_data);
1385
1386 let mut files = target_pack.files_by_paths_mut(&added_paths, false);
1387 files.par_iter_mut()
1388 .filter(|file| file.file_type() == FileType::DB || file.file_type() == FileType::Loc)
1389 .for_each(|file| {
1390 let _ = file.decode(&extra_data, true, false);
1391 });
1392 }
1393
1394 CentralCommand::send_back(&sender, Response::VecContainerPathBTreeMapStringVecContainerPath(added_paths, cut_deleted_by_pack));
1395
1396 if clipboard_is_cut {
1398 clipboard_entries.clear();
1399 clipboard_is_cut = false;
1400 }
1401 }
1402 None => CentralCommand::send_back(&sender, Response::Error(format!("Target pack not found: {}", target_key))),
1403 }
1404 }
1405 }
1406
1407 Command::DuplicatePackedFiles(pack_key, paths) => {
1409 match packs.get_mut(&pack_key) {
1410 Some(pack) => {
1411 let files_to_dup: Vec<RFile> = pack.files_by_paths(&paths, false)
1413 .into_iter()
1414 .cloned()
1415 .collect();
1416
1417 let mut added_paths = Vec::with_capacity(files_to_dup.len());
1418 for file in files_to_dup {
1419 let old_path = file.path_in_container_raw().to_string();
1420
1421 let new_path = if let Some(dot_pos) = old_path.rfind('.') {
1423 let (base, ext) = old_path.split_at(dot_pos);
1424
1425 let base_trimmed = base.trim_end_matches(|c: char| c.is_ascii_digit());
1427 let suffix_str = &base[base_trimmed.len()..];
1428 let mut counter = suffix_str.parse::<u32>().unwrap_or(0) + 1;
1429
1430 loop {
1432 let candidate = format!("{}{}{}", base_trimmed, counter, ext);
1433 if !pack.has_file(&candidate) {
1434 break candidate;
1435 }
1436 counter += 1;
1437 }
1438 } else {
1439 let base_trimmed = old_path.trim_end_matches(|c: char| c.is_ascii_digit());
1441 let suffix_str = &old_path[base_trimmed.len()..];
1442 let mut counter = suffix_str.parse::<u32>().unwrap_or(0) + 1;
1443
1444 loop {
1445 let candidate = format!("{}{}", base_trimmed, counter);
1446 if !pack.has_file(&candidate) {
1447 break candidate;
1448 }
1449 counter += 1;
1450 }
1451 };
1452
1453 let mut new_file = file;
1454 new_file.set_path_in_container_raw(&new_path);
1455
1456 if let Ok(Some(path)) = pack.insert(new_file) {
1457 added_paths.push(path);
1458 }
1459 }
1460
1461 if let Some(ref schema) = schema {
1463 let mut decode_extra_data = DecodeableExtraData::default();
1464 decode_extra_data.set_schema(Some(schema));
1465 let extra_data = Some(decode_extra_data);
1466
1467 let mut files = pack.files_by_paths_mut(&added_paths, false);
1468 files.par_iter_mut()
1469 .filter(|file| file.file_type() == FileType::DB || file.file_type() == FileType::Loc)
1470 .for_each(|file| {
1471 let _ = file.decode(&extra_data, true, false);
1472 });
1473 }
1474
1475 CentralCommand::send_back(&sender, Response::VecContainerPath(added_paths));
1476 }
1477 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1478 }
1479 }
1480
1481 Command::ExtractPackedFiles(pack_key, container_paths, path, extract_tables_to_tsv) => {
1483 let schema = if extract_tables_to_tsv { &schema } else { &None };
1484 let mut errors = 0;
1485
1486 if let Some(container_paths) = container_paths.get(&DataSource::PackFile) {
1488 match packs.get_mut(&pack_key) {
1489 Some(pack) => {
1490 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
1491 let mut extracted_paths = vec![];
1492
1493 for container_path in container_paths {
1494 match pack.extract(container_path.clone(), &path, true, schema, false, settings.bool("tables_use_old_column_order_for_tsv"), &extra_data) {
1495 Ok(mut extracted_path) => extracted_paths.append(&mut extracted_path),
1496 Err(_) => {
1497 errors += 1;
1499 },
1500 }
1501 }
1502
1503 if errors == 0 {
1504 CentralCommand::send_back(&sender, Response::StringVecPathBuf(tr("files_extracted_success"), extracted_paths));
1505 } else {
1506 CentralCommand::send_back(&sender, Response::Error(format!("There were {} errors while extracting.", errors)));
1507 }
1508 }
1509 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1510 }
1511 }
1512
1513 else {
1515
1516 let dependencies = dependencies.read().unwrap();
1517 let mut game_files = if let Some(container_paths) = container_paths.get(&DataSource::GameFiles) {
1518 dependencies.files_by_path(container_paths, true, false, false)
1519 } else {
1520 HashMap::new()
1521 };
1522 let parent_files = if let Some(container_paths) = container_paths.get(&DataSource::ParentFiles) {
1523 dependencies.files_by_path(container_paths, false, true, false)
1524 } else {
1525 HashMap::new()
1526 };
1527
1528 game_files.extend(parent_files);
1529
1530 let mut pack = Pack::default();
1531 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
1532 let mut extracted_paths = vec![];
1533 for (path_raw, file) in game_files {
1534 if pack.insert(file.clone()).is_err() {
1535 errors += 1;
1536 continue;
1537 }
1538
1539 let container_path = ContainerPath::File(path_raw);
1540 match pack.extract(container_path.clone(), &path, true, schema, false, settings.bool("tables_use_old_column_order_for_tsv"), &extra_data) {
1541 Ok(mut extracted_path) => extracted_paths.append(&mut extracted_path),
1542 Err(_) => errors += 1,
1543 }
1544
1545 pack.remove(&container_path);
1547 }
1548
1549 if errors == 0 {
1550 CentralCommand::send_back(&sender, Response::StringVecPathBuf(tr("files_extracted_success"), extracted_paths));
1551 } else {
1552 CentralCommand::send_back(&sender, Response::Error(format!("There were {} errors while extracting.", errors)));
1553 }
1554 }
1555 }
1556
1557 Command::RenamePackedFiles(pack_key, renaming_data) => {
1559 match packs.get_mut(&pack_key) {
1560 Some(pack) => {
1561 match pack.move_paths(&renaming_data) {
1562 Ok(data) => CentralCommand::send_back(&sender, Response::VecContainerPathContainerPath(data)),
1563 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1564 }
1565 }
1566 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1567 }
1568 }
1569
1570 Command::FolderExists(pack_key, path) => {
1572 match packs.get(&pack_key) {
1573 Some(pack) => CentralCommand::send_back(&sender, Response::Bool(pack.has_folder(&path))),
1574 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1575 }
1576 }
1577
1578 Command::PackedFileExists(pack_key, path) => {
1580 match packs.get(&pack_key) {
1581 Some(pack) => CentralCommand::send_back(&sender, Response::Bool(pack.has_file(&path))),
1582 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1583 }
1584 }
1585
1586 Command::GetTableListFromDependencyPackFile => {
1588 let dependencies = dependencies.read().unwrap();
1589 CentralCommand::send_back(&sender, Response::VecString(dependencies.vanilla_loose_tables().keys().chain(dependencies.vanilla_tables().keys()).map(|x| x.to_owned()).collect()))
1590 },
1591 Command::GetCustomTableList => match &schema {
1592 Some(schema) => {
1593 let tables = schema.definitions().par_iter().filter(|(key, defintions)|
1594 !defintions.is_empty() && (
1595 key.starts_with("start_pos_") ||
1596 key.starts_with("twad_")
1597 )
1598 ).map(|(key, _)| key.to_owned()).collect::<Vec<_>>();
1599 CentralCommand::send_back(&sender, Response::VecString(tables));
1600 }
1601 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string()))
1602 },
1603
1604 Command::LocalArtSetIds(_pack_key) => {
1605 CentralCommand::send_back(&sender, Response::HashSetString(dependencies.read().unwrap().db_values_from_table_name_and_column_name(Some(&packs), "campaign_character_arts_tables", "art_set_id", false, false)));
1606 }
1607
1608 Command::DependenciesArtSetIds => CentralCommand::send_back(&sender, Response::HashSetString(dependencies.read().unwrap().db_values_from_table_name_and_column_name(None, "campaign_character_arts_tables", "art_set_id", true, true))),
1610
1611 Command::GetTableVersionFromDependencyPackFile(table_name) => {
1613 if dependencies.read().unwrap().is_vanilla_data_loaded(false) {
1614 match dependencies.read().unwrap().db_version(&table_name) {
1615 Some(version) => CentralCommand::send_back(&sender, Response::I32(version)),
1616 None => {
1617
1618 if table_name.starts_with("start_pos_") || table_name.starts_with("twad_") || table_name.starts_with("ceo") {
1620 match &schema {
1621 Some(schema) => {
1622 match schema.definitions_by_table_name(&table_name) {
1623 Some(definitions) => {
1624 if definitions.is_empty() {
1625 CentralCommand::send_back(&sender, Response::Error("There are no definitions for this specific table.".to_string()));
1626 } else {
1627 CentralCommand::send_back(&sender, Response::I32(*definitions.first().unwrap().version()));
1628 }
1629 }
1630 None => CentralCommand::send_back(&sender, Response::Error("There are no definitions for this specific table.".to_string())),
1631 }
1632 }
1633 None => CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string().to_string()))
1634 }
1635 } else {
1636 CentralCommand::send_back(&sender, Response::Error("Table not found in the game files.".to_string()))
1637 }
1638 },
1639 }
1640 } else { CentralCommand::send_back(&sender, Response::Error("Dependencies cache needs to be regenerated before this.".to_string().to_string())); }
1641 }
1642
1643 Command::GetTableDefinitionFromDependencyPackFile(table_name) => {
1644 if dependencies.read().unwrap().is_vanilla_data_loaded(false) {
1645 if let Some(ref schema) = schema {
1646 if let Some(version) = dependencies.read().unwrap().db_version(&table_name) {
1647 if let Some(definition) = schema.definition_by_name_and_version(&table_name, version) {
1648 CentralCommand::send_back(&sender, Response::Definition(definition.clone()));
1649 } else { CentralCommand::send_back(&sender, Response::Error(format!("No definition found for table {}.", table_name).to_string())); }
1650 } else { CentralCommand::send_back(&sender, Response::Error(format!("Table version not found in dependencies for table {}.", table_name).to_string())); }
1651 } else { CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string().to_string())); }
1652 } else { CentralCommand::send_back(&sender, Response::Error("Dependencies cache needs to be regenerated before this.".to_string().to_string())); }
1653 }
1654
1655 Command::MergeFiles(pack_key, paths, merged_path, delete_source_files, options) => {
1657 match packs.get_mut(&pack_key) {
1658 Some(pack) => {
1659 let files_to_merge = pack.files_by_paths(&paths, false);
1660
1661 let merge_result: Result<DeltaMergeOutcome> = if *options.delta_merge() {
1662 delta_merge_files(&files_to_merge, &merged_path, &dependencies.read().unwrap(), options.resolutions())
1663 } else {
1664 RFile::merge(&files_to_merge, &merged_path).map(DeltaMergeOutcome::Merged).map_err(|error| anyhow!(error.to_string()))
1665 };
1666
1667 match merge_result {
1668 Ok(DeltaMergeOutcome::Merged(file)) => {
1669 let _ = pack.insert(file);
1670
1671 if delete_source_files {
1673 paths.iter()
1674 .filter(|path| merged_path != path.path_raw())
1675 .for_each(|path| { pack.remove(path); });
1676 }
1677
1678 CentralCommand::send_back(&sender, Response::String(merged_path.to_string()));
1679 },
1680 Ok(DeltaMergeOutcome::Conflicts(conflicts)) => CentralCommand::send_back(&sender, Response::MergeConflicts(conflicts)),
1681 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1682 }
1683 }
1684 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1685 }
1686 }
1687
1688 Command::UpdateTable(pack_key, path) => {
1690 let path = path.path_raw();
1691 match packs.get_mut(&pack_key) {
1692 Some(pack) => {
1693 if let Some(rfile) = pack.file_mut(path, false) {
1694 if let Ok(decoded) = rfile.decoded_mut() {
1695 match dependencies.write().unwrap().update_db(decoded) {
1696 Ok((old_version, new_version, fields_deleted, fields_added)) => CentralCommand::send_back(&sender, Response::I32I32VecStringVecString(old_version, new_version, fields_deleted, fields_added)),
1697 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1698 }
1699 } else { CentralCommand::send_back(&sender, Response::Error(anyhow!("File with the following path undecoded: {}", path).to_string())); }
1700 } else { CentralCommand::send_back(&sender, Response::Error(anyhow!("File not found in the open Pack: {}", path).to_string())); }
1701 }
1702 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1703 }
1704 }
1705
1706 Command::GlobalSearchReplaceMatches(_pack_key, mut global_search, matches) => {
1708 if let Some(ref schema) = schema {
1709 match global_search.replace(game, schema, &mut packs, &mut dependencies.write().unwrap(), &matches) {
1710 Ok(paths) => {
1711 let files_info = paths.iter().flat_map(|path| {
1712 packs.values().flat_map(|pack| pack.files_by_path(path, false).iter().map(|file| RFileInfo::from(*file)).collect::<Vec<RFileInfo>>()).collect::<Vec<_>>()
1713 }).collect();
1714 CentralCommand::send_back(&sender, Response::GlobalSearchVecRFileInfo(Box::new(global_search), files_info));
1715 }
1716 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1717 }
1718 } else {
1719 CentralCommand::send_back(&sender, Response::Error(anyhow!("Schema not found. Maybe you need to download it?").to_string()));
1720 }
1721 }
1722
1723 Command::GlobalSearchReplaceAll(_pack_key, mut global_search) => {
1725 if let Some(ref schema) = schema {
1726 match global_search.replace_all(game, schema, &mut packs, &mut dependencies.write().unwrap()) {
1727 Ok(paths) => {
1728 let files_info = paths.iter().flat_map(|path| {
1729 packs.values().flat_map(|pack| pack.files_by_path(path, false).iter().map(|file| RFileInfo::from(*file)).collect::<Vec<RFileInfo>>()).collect::<Vec<_>>()
1730 }).collect();
1731 CentralCommand::send_back(&sender, Response::GlobalSearchVecRFileInfo(Box::new(global_search), files_info));
1732 }
1733 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1734 }
1735 } else {
1736 CentralCommand::send_back(&sender, Response::Error(anyhow!("Schema not found. Maybe you need to download it?").to_string()));
1737 }
1738 }
1739
1740 Command::GetReferenceDataFromDefinition(_pack_key, table_name, definition, force_local_ref_generation) => {
1742 let mut reference_data = HashMap::new();
1743
1744 if let Some(ref schema) = schema {
1746 if dependencies.read().unwrap().local_tables_references().get(&table_name).is_none() || force_local_ref_generation {
1747 dependencies.write().unwrap().generate_local_definition_references(schema, &table_name, &definition);
1748 }
1749
1750 reference_data = dependencies.read().unwrap().db_reference_data(schema, &packs, &table_name, &definition, &None);
1751 }
1752
1753 CentralCommand::send_back(&sender, Response::HashMapI32TableReferences(reference_data));
1754 }
1755
1756 Command::SetVideoFormat(pack_key, path, format) => {
1758 match packs.get_mut(&pack_key) {
1759 Some(pack) => {
1760 match pack.files_mut().get_mut(&path) {
1761 Some(ref mut rfile) => {
1762 match rfile.decoded_mut() {
1763 Ok(data) => {
1764 if let RFileDecoded::Video(ref mut data) = data {
1765 data.set_format(format);
1766 CentralCommand::send_back(&sender, Response::Success);
1767 } else {
1768 CentralCommand::send_back(&sender, Response::Error("The file is not a video.".to_string()));
1769 }
1770 }
1771 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1772 }
1773 }
1774 None => CentralCommand::send_back(&sender, Response::Error("This Pack doesn't exists as a file in the disk.".to_string())),
1775 }
1776 }
1777 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1778 }
1779 },
1780
1781 Command::SaveSchema(mut schema_new) => {
1783 match schema_new.save(&schemas_path().unwrap().join(game.schema_file_name())) {
1784 Ok(_) => {
1785 schema = Some(schema_new);
1786 CentralCommand::send_back(&sender, Response::Success);
1787 },
1788 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1789 }
1790 }
1791
1792 Command::CleanCache(pack_key, paths) => {
1794 match packs.get_mut(&pack_key) {
1795 Some(pack) => {
1796 let cf = pack.compression_format();
1797 let mut files = pack.files_by_paths_mut(&paths, false);
1798 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
1799
1800 files.iter_mut().for_each(|file| {
1801 let _ = file.encode(&extra_data, true, true, false);
1802 });
1803 CentralCommand::send_back(&sender, Response::Success);
1804 }
1805 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1806 }
1807 }
1808
1809 Command::ExportTSV(pack_key, internal_path, external_path, data_source) => {
1811 let mut dependencies = dependencies.write().unwrap();
1812 match &schema {
1813 Some(ref schema) => {
1814 let file = match data_source {
1815 DataSource::PackFile => packs.get_mut(&pack_key).and_then(|pack| pack.file_mut(&internal_path, false)),
1816 DataSource::ParentFiles => dependencies.file_mut(&internal_path, false, true).ok(),
1817 DataSource::GameFiles => dependencies.file_mut(&internal_path, true, false).ok(),
1818 DataSource::AssKitFiles => {
1819 CentralCommand::send_back(&sender, Response::Error("Exporting a TSV from the Assembly Kit is not yet supported.".to_string()));
1820 continue;
1821 },
1822 DataSource::ExternalFile => {
1823 CentralCommand::send_back(&sender, Response::Error("Exporting a TSV from a external file is not yet supported.".to_string()));
1824 continue;
1825 },
1826 };
1827 match file {
1828 Some(file) => match file.tsv_export_to_path(&external_path, schema, settings.bool("tables_use_old_column_order_for_tsv")) {
1829 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
1830 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1831 }
1832 None => CentralCommand::send_back(&sender, Response::Error(format!("File with the following path not found in the Pack: {}", internal_path).to_string())),
1833 }
1834 },
1835 None => CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string().to_string())),
1836 }
1837 }
1838
1839 Command::ImportTSV(pack_key, internal_path, external_path) => {
1842 match packs.get_mut(&pack_key) {
1843 Some(pack) => {
1844 match pack.file_mut(&internal_path, false) {
1845 Some(file) => {
1846 let original_guid = if let Ok(RFileDecoded::DB(table)) = file.decoded() {
1849 Some(table.guid().to_owned())
1850 } else {
1851 None
1852 };
1853
1854 match RFile::tsv_import_from_path(&external_path, &schema) {
1855 Ok(imported) => {
1856 let mut decoded = imported.decoded().unwrap().clone();
1857 if let (RFileDecoded::DB(table), Some(guid)) = (&mut decoded, original_guid) {
1858 table.set_guid(guid);
1859 }
1860 file.set_decoded(decoded.clone()).unwrap();
1861 CentralCommand::send_back(&sender, Response::RFileDecoded(decoded))
1862 },
1863 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1864 }
1865 }
1866 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("File with the following path not found in the Pack: {}", internal_path).to_string())),
1867 }
1868 }
1869 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1870 }
1871 }
1872
1873 Command::OpenContainingFolder(pack_key) => {
1875 match packs.get(&pack_key) {
1876 Some(pack) => {
1877
1878 let mut path_str = pack.disk_file_path().to_owned();
1880
1881 if path_str.starts_with("//?/") || path_str.starts_with("\\\\?\\") {
1883 path_str = path_str[4..].to_string();
1884 }
1885
1886 let mut path = PathBuf::from(path_str);
1887 if path.exists() {
1888 path.pop();
1889 let _ = open::that(&path);
1890 CentralCommand::send_back(&sender, Response::Success);
1891 }
1892 else {
1893 CentralCommand::send_back(&sender, Response::Error("This Pack doesn't exists as a file in the disk.".to_string()));
1894 }
1895 }
1896 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1897 }
1898 },
1899
1900 Command::OpenPackedFileInExternalProgram(pack_key, data_source, path) => {
1902 match data_source {
1903 DataSource::PackFile => {
1904 match packs.get_mut(&pack_key) {
1905 Some(pack) => {
1906 let folder = temp_dir().join(format!("rpfm_{}", pack.disk_file_name()));
1907 let cf = pack.compression_format();
1908 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
1909
1910 match pack.extract(path.clone(), &folder, true, &schema, false, settings.bool("tables_use_old_column_order_for_tsv"), &extra_data) {
1911 Ok(extracted_path) => {
1912 let _ = that(&extracted_path[0]);
1913 CentralCommand::send_back(&sender, Response::PathBuf(extracted_path[0].to_owned()));
1914 }
1915 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1916 }
1917 }
1918 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1919 }
1920 }
1921 _ => CentralCommand::send_back(&sender, Response::Error(anyhow!("Opening dependencies files in external programs is not yet supported.").to_string())),
1922 }
1923 }
1924
1925 Command::SavePackedFileFromExternalView(pack_key, path, external_path) => {
1927 match packs.get_mut(&pack_key) {
1928 Some(pack) => {
1929 match pack.file_mut(&path, false) {
1930 Some(file) => match file.encode_from_external_data(&schema, &external_path) {
1931 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
1932 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1933 }
1934 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("File not found").to_string())),
1935 }
1936 }
1937 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
1938 }
1939 }
1940
1941 Command::GetPluginScripts => {
1943 match scripts_path() {
1944 Ok(folder) => {
1945 let mut scripts = vec![];
1946 if let Ok(entries) = std::fs::read_dir(&folder) {
1947 for entry in entries.flatten() {
1948 let path = entry.path();
1949 if path.is_file() && plugin_script_interpreter(&path).is_some() {
1950 scripts.push(path.to_string_lossy().to_string());
1951 }
1952 }
1953 }
1954
1955 scripts.sort();
1956 CentralCommand::send_back(&sender, Response::VecString(scripts));
1957 }
1958 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
1959 }
1960 }
1961
1962 Command::RunPluginScript(pack_key, script_path, container_paths) => {
1964 let interpreter = match plugin_script_interpreter(&script_path) {
1965 Some(interpreter) => interpreter,
1966 None => {
1967 CentralCommand::send_back(&sender, Response::Error(format!("Unsupported plugin script type: {}", script_path.display())));
1968 continue 'background_loop;
1969 }
1970 };
1971
1972 match packs.get_mut(&pack_key) {
1973 Some(pack) => {
1974
1975 let base_folder = temp_dir().join("rpfm_plugins").join(pack.disk_file_name());
1979 let _ = std::fs::remove_dir_all(&base_folder);
1980
1981 let cf = pack.compression_format();
1982 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
1983 let keys_first = settings.bool("tables_use_old_column_order_for_tsv");
1984
1985 let mut extracted_paths = vec![];
1986 let mut extract_failed = false;
1987 for container_path in &container_paths {
1988 match pack.extract(container_path.clone(), &base_folder, true, &schema, false, keys_first, &extra_data) {
1989 Ok(mut paths) => extracted_paths.append(&mut paths),
1990 Err(error) => {
1991 CentralCommand::send_back(&sender, Response::Error(format!("Error extracting files for the plugin script: {}", error)));
1992 extract_failed = true;
1993 break;
1994 }
1995 }
1996 }
1997
1998 if extract_failed {
1999 continue 'background_loop;
2000 }
2001
2002 let output = std::process::Command::new(interpreter)
2004 .arg(&script_path)
2005 .args(&extracted_paths)
2006 .current_dir(&base_folder)
2007 .output();
2008
2009 let message = match output {
2010 Ok(output) => {
2011 let stdout = String::from_utf8_lossy(&output.stdout);
2012 let stderr = String::from_utf8_lossy(&output.stderr);
2013
2014 let report = format!("Script: {}\nStatus: {}\n\n--- stdout ---\n{}\n--- stderr ---\n{}\n", script_path.display(), output.status, stdout, stderr);
2017 if let Ok(folder) = scripts_path() {
2018 let _ = std::fs::write(folder.join("last_run.log"), report.as_bytes());
2019 }
2020
2021 info!("Plugin script {} finished with {}.", script_path.display(), output.status);
2022 if !stderr.trim().is_empty() {
2023 warn!("Plugin script stderr:\n{}", stderr.trim());
2024 }
2025
2026 if output.status.success() {
2027 None
2028 } else {
2029 Some(format!("The plugin script finished with errors. See last_run.log in the scripts folder.\n\n{}", stderr.trim()))
2030 }
2031 }
2032 Err(error) => {
2033 error!("Failed to run the plugin script {}: {}", script_path.display(), error);
2034 CentralCommand::send_back(&sender, Response::Error(format!("Failed to run the plugin script: {}", error)));
2035 continue 'background_loop;
2036 }
2037 };
2038
2039 let mut reimported_paths = vec![];
2041 for disk_path in &extracted_paths {
2042 if !disk_path.is_file() {
2043 continue;
2044 }
2045
2046 let direct_path = container_path_from_disk_path(disk_path, &base_folder);
2049 let container_path = if pack.file_mut(&direct_path, false).is_some() {
2050 direct_path
2051 } else if let Some(stripped) = direct_path.strip_suffix(".tsv") {
2052 stripped.to_owned()
2053 } else {
2054 direct_path
2055 };
2056
2057 if let Some(file) = pack.file_mut(&container_path, false) {
2058 if file.encode_from_external_data(&schema, disk_path).is_ok() {
2059 reimported_paths.push(ContainerPath::File(container_path));
2060 }
2061 }
2062 }
2063
2064 let _ = std::fs::remove_dir_all(&base_folder);
2065 CentralCommand::send_back(&sender, Response::VecContainerPathOptionString(reimported_paths, message));
2066 }
2067 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2068 }
2069 }
2070
2071 Command::UpdateSchemas => {
2073
2074 let git_result = tokio::task::spawn_blocking(|| {
2076 match schemas_path() {
2077 Ok(local_path) => {
2078 let git_integration = GitIntegration::new(&local_path, SCHEMA_REPO, SCHEMA_BRANCH, SCHEMA_REMOTE);
2079 git_integration.update_repo().map(|_| ()).map_err(|e| anyhow::anyhow!(e.to_string()))
2080 },
2081 Err(error) => Err(error),
2082 }
2083 }).await.unwrap();
2084
2085 match git_result {
2087 Ok(_) => {
2088 let schema_path = schemas_path().unwrap().join(game.schema_file_name());
2089 let patches_path = table_patches_path().unwrap().join(game.schema_file_name());
2090
2091 for pack in packs.values_mut() {
2093 let cf = pack.compression_format();
2094 let mut tables = pack.files_by_type_mut(&[FileType::DB]);
2095 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
2096
2097 tables.par_iter_mut().for_each(|x| { let _ = x.encode(&extra_data, true, true, false); });
2098 }
2099
2100 schema = Schema::load(&schema_path, Some(&patches_path)).ok();
2101
2102 for pack in packs.values_mut() {
2103 let mut extra_data = DecodeableExtraData::default();
2104 extra_data.set_schema(schema.as_ref());
2105 let extra_data = Some(extra_data);
2106
2107 let mut tables = pack.files_by_type_mut(&[FileType::DB]);
2108 tables.par_iter_mut().for_each(|x| {
2109 let _ = x.decode(&extra_data, true, false);
2110 });
2111 }
2112
2113 if dependencies.read().unwrap().is_vanilla_data_loaded(false) {
2115 let game_path = settings.path_buf(game.key());
2116 let secondary_path = settings.path_buf(SECONDARY_PATH);
2117 let dependencies_file_path = dependencies_cache_path().unwrap().join(game.dependencies_cache_file_name());
2118 let pack_dependencies: Vec<_> = packs.values()
2119 .flat_map(|pack| pack.dependencies().iter().map(|x| x.1.clone()))
2120 .collect();
2121
2122 match dependencies.write().unwrap().rebuild(&schema, &pack_dependencies, Some(&*dependencies_file_path), game, &game_path, &secondary_path) {
2123 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
2124 Err(_) => CentralCommand::send_back(&sender, Response::Error("Schema updated, but dependencies cache rebuilding failed. You may need to regenerate it.".to_string())),
2125 }
2126 } else {
2127 CentralCommand::send_back(&sender, Response::Success)
2128 }
2129 },
2130 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2131 }
2132 }
2133
2134 Command::UpdateLuaAutogen => {
2136 let sender = sender.clone();
2137 tokio::spawn(async move {
2138 let result = tokio::task::spawn_blocking(|| {
2139 match lua_autogen_base_path() {
2140 Ok(local_path) => {
2141 let git_integration = GitIntegration::new(&local_path, LUA_REPO, LUA_BRANCH, LUA_REMOTE);
2142 git_integration.update_repo().map(|_| ()).map_err(|e| e.into())
2143 },
2144 Err(error) => Err(error),
2145 }
2146 }).await.unwrap();
2147
2148 match result {
2149 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
2150 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2151 }
2152 });
2153 }
2154
2155 Command::UpdateMainProgram => {
2157 let sender = sender.clone();
2158 let settings = settings.clone();
2159 tokio::spawn(async move {
2160 let result = tokio::task::spawn_blocking(move || {
2161 crate::updater::update_main_program(&settings)
2162 }).await.unwrap();
2163
2164 match result {
2165 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
2166 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2167 }
2168 });
2169 }
2170
2171 Command::TriggerBackupAutosave(pack_key) => {
2173 match packs.get(&pack_key) {
2174 Some(pack) => {
2175 let folder = backup_autosave_path().unwrap().join(pack.disk_file_name());
2176 let _ = DirBuilder::new().recursive(true).create(&folder);
2177
2178 let game_path = settings.path_buf(game.key());
2179 let ca_paths = game.ca_packs_paths(&game_path)
2180 .unwrap_or_default()
2181 .iter()
2182 .map(|path| path.to_string_lossy().replace('\\', "/"))
2183 .collect::<Vec<_>>();
2184
2185 let pack_disable_autosaves = pack.settings().setting_bool("disable_autosaves")
2186 .unwrap_or(&true);
2187
2188 let pack_type = pack.pfh_file_type();
2189 let pack_path = pack.disk_file_path().replace('\\', "/");
2190
2191 if folder.is_dir() &&
2193 !pack_disable_autosaves &&
2194 (pack_type == PFHFileType::Mod || pack_type == PFHFileType::Movie) &&
2195 (ca_paths.is_empty() || !ca_paths.contains(&pack_path))
2196 {
2197 let date = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
2198 let new_name = format!("{date}.pack");
2199 let new_path = folder.join(new_name);
2200 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, pack.compression_format(), settings.bool("disable_uuid_regeneration_on_db_tables")));
2201 let _ = pack.clone().save(Some(&new_path), game, &extra_data);
2202
2203 if let Ok(files) = files_in_folder_from_newest_to_oldest(&folder) {
2205 let max_files = settings.i32("autosave_amount") as usize;
2206 for (index, file) in files.iter().enumerate() {
2207 if index >= max_files {
2208 let _ = std::fs::remove_file(file);
2209 }
2210 }
2211 }
2212 }
2213 CentralCommand::send_back(&sender, Response::Success);
2214 }
2215 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2216 }
2217 }
2218
2219 Command::DiagnosticsCheck(diagnostics_ignored, check_ak_only_refs) => {
2221 let game_path = settings.path_buf(game.key());
2222 let mut diagnostics = Diagnostics::default();
2223 *diagnostics.diagnostics_ignored_mut() = diagnostics_ignored;
2224
2225 if let Some(ref schema) = schema {
2226 diagnostics.check(&mut packs, &mut dependencies.write().unwrap(), schema, game, &game_path, &[], check_ak_only_refs);
2227 }
2228
2229 info!("Checking diagnostics: done.");
2230
2231 CentralCommand::send_back(&sender, Response::Diagnostics(diagnostics));
2232 }
2233
2234 Command::DiagnosticsUpdate(mut diagnostics, path_types, check_ak_only_refs) => {
2235 let game_path = settings.path_buf(game.key());
2236
2237 if let Some(ref schema) = schema {
2238 diagnostics.check(&mut packs, &mut dependencies.write().unwrap(), schema, game, &game_path, &path_types, check_ak_only_refs);
2239 }
2240
2241 info!("Checking diagnostics (update): done.");
2242
2243 CentralCommand::send_back(&sender, Response::Diagnostics(diagnostics));
2244 }
2245
2246 Command::GetPackSettings(pack_key) => {
2248 match packs.get(&pack_key) {
2249 Some(pack) => CentralCommand::send_back(&sender, Response::PackSettings(pack.settings().clone())),
2250 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2251 }
2252 }
2253 Command::SetPackSettings(pack_key, pack_settings) => {
2254 match packs.get_mut(&pack_key) {
2255 Some(pack) => {
2256 pack.set_settings(pack_settings);
2257 CentralCommand::send_back(&sender, Response::Success);
2258 }
2259 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2260 }
2261 }
2262
2263 Command::GetMissingDefinitions(pack_key) => {
2264 match packs.get_mut(&pack_key) {
2265 Some(pack) => {
2266 let mut counter = 0;
2269 let mut table_list = String::new();
2270 if let Some(ref schema) = schema {
2271 let mut extra_data = DecodeableExtraData::default();
2272 extra_data.set_schema(Some(schema));
2273 let extra_data = Some(extra_data);
2274
2275 let mut files = pack.files_by_type_mut(&[FileType::DB]);
2276 files.sort_by_key(|file| file.path_in_container_raw().to_lowercase());
2277
2278 for file in files {
2279 if file.decode(&extra_data, false, false).is_err() && file.load().is_ok() {
2280 if let Ok(raw_data) = file.cached() {
2281 let mut reader = Cursor::new(raw_data);
2282 if let Ok((_, _, _, entry_count)) = DB::read_header(&mut reader) {
2283 if entry_count > 0 {
2284 counter += 1;
2285 table_list.push_str(&format!("{}, {:?}\n", counter, file.path_in_container_raw()))
2286 }
2287 }
2288 }
2289 }
2290 }
2291 }
2292
2293 let path = exe_path().join("missing_table_definitions.txt");
2295
2296 if let Ok(file) = File::create(path) {
2297 let mut file = BufWriter::new(file);
2298 let _ = file.write_all(table_list.as_bytes());
2299 }
2300 CentralCommand::send_back(&sender, Response::Success);
2301 }
2302 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2303 }
2304 }
2305
2306 Command::RebuildDependencies(rebuild_only_current_mod_dependencies) => {
2308 if schema.is_some() {
2309 let game_path = settings.path_buf(game.key());
2310 let dependencies_file_path = dependencies_cache_path().unwrap().join(game.dependencies_cache_file_name());
2311 let file_path = if !rebuild_only_current_mod_dependencies { Some(&*dependencies_file_path) } else { None };
2312 let pack_dependencies: Vec<_> = packs.values()
2313 .flat_map(|pack| pack.dependencies().iter().map(|x| x.1.clone()))
2314 .collect();
2315
2316 let secondary_path = settings.path_buf(SECONDARY_PATH);
2317 let _ = dependencies.write().unwrap().rebuild(&schema, &pack_dependencies, file_path, game, &game_path, &secondary_path);
2318 let dependencies_info = DependenciesInfo::new(&dependencies.read().unwrap(), game.vanilla_db_table_name_logic());
2319 CentralCommand::send_back(&sender, Response::DependenciesInfo(dependencies_info));
2320 } else {
2321 CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string()));
2322 }
2323 },
2324
2325 Command::CascadeEdition(pack_key, table_name, definition, changes) => {
2326 match packs.get_mut(&pack_key) {
2327 Some(pack) => {
2328 let edited_paths = if let Some(ref schema) = schema {
2329 changes.iter().flat_map(|(field, value_before, value_after)| {
2330 DB::cascade_edition(pack, schema, &table_name, field, &definition, value_before, value_after)
2331 }).collect::<Vec<_>>()
2332 } else { vec![] };
2333
2334 let packed_files_info = pack.files_by_paths(&edited_paths, false).into_par_iter().map(From::from).collect();
2335 CentralCommand::send_back(&sender, Response::VecContainerPathVecRFileInfo(edited_paths, packed_files_info));
2336 }
2337 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2338 }
2339 },
2340
2341 Command::GetTablesByTableName(pack_key, table_name) => {
2342 match packs.get(&pack_key) {
2343 Some(pack) => {
2344 let path = ContainerPath::Folder(format!("db/{table_name}/"));
2345 let files = pack.files_by_type_and_paths(&[FileType::DB], &[path], true);
2346 let paths = files.iter()
2347 .map(|x| x.path_in_container_raw().to_owned())
2348 .collect::<Vec<_>>();
2349
2350 CentralCommand::send_back(&sender, Response::VecString(paths));
2351 }
2352 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2353 }
2354 },
2355
2356 Command::AddKeysToKeyDeletes(pack_key, table_file_name, key_table_name, keys) => {
2357 match packs.get_mut(&pack_key) {
2358 Some(pack) => {
2359 let path = ContainerPath::File(format!("db/{KEY_DELETES_TABLE_NAME}/{table_file_name}"));
2360 let mut files = pack.files_by_type_and_paths_mut(&[FileType::DB], &[path], true);
2361
2362 let mut cont_path = None;
2363 if let Some(file) = files.first_mut() {
2364 if let Ok(RFileDecoded::DB(db)) = file.decoded_mut() {
2365 for key in &keys {
2366 let row = vec![
2367 DecodedData::StringU8(key.to_owned()),
2368 DecodedData::StringU8(key_table_name.to_owned()),
2369 ];
2370
2371 db.data_mut().push(row);
2372 }
2373
2374 cont_path = Some(file.path_in_container());
2375 }
2376 }
2377
2378 CentralCommand::send_back(&sender, Response::OptionContainerPath(cont_path));
2379 }
2380 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2381 }
2382 }
2383
2384 Command::GoToDefinition(pack_key, ref_table, mut ref_column, ref_data) => {
2385 let table_name = format!("{ref_table}_tables");
2386 let table_folder = format!("db/{table_name}");
2387 let mut found = false;
2388
2389 let mut packs_to_search: Vec<&Pack> = Vec::with_capacity(packs.len());
2391 if let Some(pack) = packs.get(&pack_key) {
2392 packs_to_search.push(pack);
2393 }
2394 packs_to_search.extend(packs.iter().filter(|kv| kv.0 != &pack_key).map(|kv| kv.1));
2395
2396 for pack in packs_to_search {
2397 let packed_files = pack.files_by_path(&ContainerPath::Folder(table_folder.to_owned()), true);
2398 for packed_file in &packed_files {
2399 if let Ok(RFileDecoded::DB(data)) = packed_file.decoded() {
2400
2401 if data.definition().localised_fields().iter().any(|x| x.name() == ref_column) {
2403 if let Some(first_key_index) = data.definition().localised_key_order().first() {
2404 if let Some(first_key_field) = data.definition().fields_processed().get(*first_key_index as usize) {
2405 ref_column = first_key_field.name().to_owned();
2406 }
2407 }
2408 }
2409
2410 if let Some((column_index, row_index)) = data.table().rows_containing_data(&ref_column, &ref_data[0]) {
2411 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::PackFile, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2412 found = true;
2413 break;
2414 }
2415 }
2416 }
2417
2418 if found {
2419 break;
2420 }
2421 }
2422
2423 if !found {
2424 if let Ok(packed_files) = dependencies.read().unwrap().db_data(&table_name, false, true) {
2425 for packed_file in &packed_files {
2426 if let Ok(RFileDecoded::DB(data)) = packed_file.decoded() {
2427
2428 if data.definition().localised_fields().iter().any(|x| x.name() == ref_column) {
2430 if let Some(first_key_index) = data.definition().localised_key_order().first() {
2431 if let Some(first_key_field) = data.definition().fields_processed().get(*first_key_index as usize) {
2432 ref_column = first_key_field.name().to_owned();
2433 }
2434 }
2435 }
2436
2437 if let Some((column_index, row_index)) = data.table().rows_containing_data(&ref_column, &ref_data[0]) {
2438 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::ParentFiles, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2439 found = true;
2440 break;
2441 }
2442 }
2443 }
2444 }
2445 }
2446
2447 if !found {
2448 if let Ok(packed_files) = dependencies.read().unwrap().db_data(&table_name, true, false) {
2449 for packed_file in &packed_files {
2450 if let Ok(RFileDecoded::DB(data)) = packed_file.decoded() {
2451
2452 if data.definition().localised_fields().iter().any(|x| x.name() == ref_column) {
2454 if let Some(first_key_index) = data.definition().localised_key_order().first() {
2455 if let Some(first_key_field) = data.definition().fields_processed().get(*first_key_index as usize) {
2456 ref_column = first_key_field.name().to_owned();
2457 }
2458 }
2459 }
2460
2461 if let Some((column_index, row_index)) = data.table().rows_containing_data(&ref_column, &ref_data[0]) {
2462 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::GameFiles, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2463 found = true;
2464 break;
2465 }
2466 }
2467 }
2468 }
2469 }
2470
2471 if !found {
2472 if let Some(data) = dependencies.read().unwrap().asskit_only_db_tables().get(&table_name) {
2473
2474 if data.definition().localised_fields().iter().any(|x| x.name() == ref_column) {
2476 if let Some(first_key_index) = data.definition().localised_key_order().first() {
2477 if let Some(first_key_field) = data.definition().fields_processed().get(*first_key_index as usize) {
2478 ref_column = first_key_field.name().to_owned();
2479 }
2480 }
2481 }
2482
2483 if let Some((column_index, row_index)) = data.table().rows_containing_data(&ref_column, &ref_data[0]) {
2484 let path = format!("{}/ak_data", &table_folder);
2485 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::AssKitFiles, path, column_index, row_index[0]));
2486 found = true;
2487 }
2488 }
2489 }
2490
2491 if !found {
2492 CentralCommand::send_back(&sender, Response::Error(tr("source_data_for_field_not_found")));
2493 }
2494 },
2495
2496 Command::SearchReferences(pack_key, reference_map, value) => {
2497 let paths = reference_map.keys().map(|x| ContainerPath::Folder(format!("db/{x}"))).collect::<Vec<ContainerPath>>();
2498 let Some(pack) = get_pack(&packs, &pack_key, &sender) else { continue 'background_loop; };
2499 let files = pack.files_by_paths(&paths, true);
2500
2501 let mut references: Vec<(DataSource, String, String, String, usize, usize)> = vec![];
2502
2503 for (table_name, columns) in &reference_map {
2506 for file in &files {
2507 if file.db_table_name_from_path().unwrap() == table_name {
2508 if let Ok(RFileDecoded::DB(data)) = file.decoded() {
2509 for column_name in columns {
2510 if let Some((column_index, row_indexes)) = data.table().rows_containing_data(column_name, &value) {
2511 for row_index in &row_indexes {
2512 references.push((DataSource::PackFile, pack_key.clone(), file.path_in_container_raw().to_owned(), column_name.to_owned(), column_index, *row_index));
2513 }
2514 }
2515 }
2516 }
2517 }
2518 }
2519 }
2520
2521 for (table_name, columns) in &reference_map {
2525 if let Ok(tables) = dependencies.read().unwrap().db_data(table_name, false, true) {
2526 references.append(&mut tables.par_iter().map(|table| {
2527 let mut references = vec![];
2528 if let Ok(RFileDecoded::DB(data)) = table.decoded() {
2529 for column_name in columns {
2530 if let Some((column_index, row_indexes)) = data.table().rows_containing_data(column_name, &value) {
2531 for row_index in &row_indexes {
2532 references.push((DataSource::ParentFiles, String::new(), table.path_in_container_raw().to_owned(), column_name.to_owned(), column_index, *row_index));
2533 }
2534 }
2535 }
2536 }
2537
2538 references
2539 }).flatten().collect());
2540 }
2541 }
2542
2543 for (table_name, columns) in &reference_map {
2545 if let Ok(tables) = dependencies.read().unwrap().db_data(table_name, true, false) {
2546 references.append(&mut tables.par_iter().map(|table| {
2547 let mut references = vec![];
2548 if let Ok(RFileDecoded::DB(data)) = table.decoded() {
2549 for column_name in columns {
2550 if let Some((column_index, row_indexes)) = data.table().rows_containing_data(column_name, &value) {
2551 for row_index in &row_indexes {
2552 references.push((DataSource::GameFiles, String::new(), table.path_in_container_raw().to_owned(), column_name.to_owned(), column_index, *row_index));
2553 }
2554 }
2555 }
2556 }
2557
2558 references
2559 }).flatten().collect());
2560 }
2561 }
2562
2563 CentralCommand::send_back(&sender, Response::VecDataSourceStringStringStringUsizeUsize(references));
2564 },
2565
2566 Command::GoToLoc(pack_key, loc_key) => {
2567 let Some(pack) = get_pack(&packs, &pack_key, &sender) else { continue 'background_loop; };
2568 let packed_files = pack.files_by_type(&[FileType::Loc]);
2569 let mut found = false;
2570 for packed_file in &packed_files {
2571 if let Ok(RFileDecoded::Loc(data)) = packed_file.decoded() {
2572 if let Some((column_index, row_index)) = data.table().rows_containing_data("key", &loc_key) {
2573 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::PackFile, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2574 found = true;
2575 break;
2576 }
2577 }
2578 }
2579
2580 if !found {
2581 if let Ok(packed_files) = dependencies.read().unwrap().loc_data(false, true) {
2582 for packed_file in &packed_files {
2583 if let Ok(RFileDecoded::Loc(data)) = packed_file.decoded() {
2584 if let Some((column_index, row_index)) = data.table().rows_containing_data("key", &loc_key) {
2585 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::ParentFiles, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2586 found = true;
2587 break;
2588 }
2589 }
2590 }
2591 }
2592 }
2593
2594 if !found {
2595 if let Ok(packed_files) = dependencies.read().unwrap().loc_data(true, false) {
2596 for packed_file in &packed_files {
2597 if let Ok(RFileDecoded::Loc(data)) = packed_file.decoded() {
2598 if let Some((column_index, row_index)) = data.table().rows_containing_data("key", &loc_key) {
2599 CentralCommand::send_back(&sender, Response::DataSourceStringUsizeUsize(DataSource::GameFiles, packed_file.path_in_container_raw().to_owned(), column_index, row_index[0]));
2600 found = true;
2601 break;
2602 }
2603 }
2604 }
2605 }
2606 }
2607
2608 if !found {
2609 CentralCommand::send_back(&sender, Response::Error(tr("loc_key_not_found")));
2610 }
2611 },
2612
2613 Command::GetSourceDataFromLocKey(_pack_key, loc_key) => CentralCommand::send_back(&sender, Response::OptionStringStringVecString(dependencies.read().unwrap().loc_key_source(&loc_key))),
2614 Command::GetPackFileName(pack_key) => {
2615 match packs.get(&pack_key) {
2616 Some(pack) => CentralCommand::send_back(&sender, Response::String(pack.disk_file_name())),
2617 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2618 }
2619 }
2620 Command::GetPackedFileRawData(pack_key, path) => {
2621 match packs.get_mut(&pack_key) {
2622 Some(pack) => {
2623 let cf = pack.compression_format();
2624 match pack.files_mut().get_mut(&path) {
2625 Some(ref mut rfile) => {
2626
2627 match rfile.load() {
2629 Ok(_) => match rfile.cached() {
2630 Ok(data) => CentralCommand::send_back(&sender, Response::VecU8(data.to_vec())),
2631
2632 Err(_) => {
2636 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
2637 match rfile.encode(&extra_data, false, false, true) {
2638 Ok(data) => CentralCommand::send_back(&sender, Response::VecU8(data.unwrap())),
2639 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2640 }
2641 },
2642 },
2643 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2644 }
2645 }
2646 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("This PackedFile no longer exists in the PackFile.").to_string())),
2647 }
2648 }
2649 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2650 }
2651 },
2652
2653 Command::ImportDependenciesToOpenPackFile(pack_key, paths_by_data_source) => {
2654 match packs.get_mut(&pack_key) {
2655 Some(pack) => {
2656 let mut added_paths = vec![];
2657 let mut not_added_paths = vec![];
2658
2659 let dependencies = dependencies.read().unwrap();
2660 for (data_source, paths) in &paths_by_data_source {
2661 let files = match data_source {
2662 DataSource::GameFiles => dependencies.files_by_path(paths, true, false, false),
2663 DataSource::ParentFiles => dependencies.files_by_path(paths, false, true, false),
2664 DataSource::AssKitFiles => HashMap::new(),
2665 _ => {
2666 CentralCommand::send_back(&sender, Response::Error("You can't import files from this source.".to_string()));
2667 continue 'background_loop;
2668 },
2669 };
2670
2671 for file in files.into_values() {
2672 let file_path = file.path_in_container_raw().to_owned();
2673 let mut file = file.clone();
2674 let _ = file.guess_file_type();
2675 if let Ok(Some(path)) = pack.insert(file) {
2676 added_paths.push(path);
2677 } else {
2678 not_added_paths.push(file_path);
2679 }
2680 }
2681 }
2682
2683 for (data_source, paths) in &paths_by_data_source {
2685 match data_source {
2686 DataSource::GameFiles | DataSource::ParentFiles => {},
2687 DataSource::AssKitFiles => {
2688 match &schema {
2689 Some(ref schema) => {
2690 let mut files = vec![];
2691 for path in paths {
2692
2693 match path {
2695 ContainerPath::Folder(path) => {
2696 let mut path = path.to_owned();
2697
2698 if path.ends_with('/') {
2699 path.pop();
2700 }
2701
2702 let path_split = path.split('/').collect::<Vec<_>>();
2703 let table_name_logic = game.vanilla_db_table_name_logic();
2704
2705 if path_split.len() == 1 {
2707 let table_names = dependencies.asskit_only_db_tables().keys();
2708 for table_name in table_names {
2709 let table_file_name = match table_name_logic {
2710 VanillaDBTableNameLogic::DefaultName(ref name) => name,
2711 VanillaDBTableNameLogic::FolderName => table_name,
2712 };
2713
2714 match dependencies.import_from_ak(table_name, schema) {
2715 Ok(table) => {
2716 let mut path = path_split.to_vec();
2717 path.push(table_file_name);
2718 let mut path = path.join("/");
2719
2720 if table_name.starts_with("ceo") {
2721 path = format!("ceo_{path}");
2722 }
2723
2724 let file = RFile::new_from_decoded(&RFileDecoded::DB(table), 0, &path);
2725 files.push(file);
2726 },
2727 Err(_) => not_added_paths.push(path.clone()),
2728 }
2729 }
2730 }
2731
2732 else if path_split.len() == 2 {
2734
2735 let table_name = path_split[1];
2736 let table_file_name = match table_name_logic {
2737 VanillaDBTableNameLogic::DefaultName(ref name) => name,
2738 VanillaDBTableNameLogic::FolderName => table_name,
2739 };
2740
2741 match dependencies.import_from_ak(table_name, schema) {
2742 Ok(table) => {
2743 let mut path = path_split.to_vec();
2744 path.push(table_file_name);
2745 let mut path = path.join("/");
2746
2747 if table_name.starts_with("ceo") {
2748 path = format!("ceo_{path}");
2749 }
2750
2751 let file = RFile::new_from_decoded(&RFileDecoded::DB(table), 0, &path);
2752 files.push(file);
2753 },
2754 Err(_) => not_added_paths.push(path.clone()),
2755 }
2756 }
2757
2758 else {
2760 CentralCommand::send_back(&sender, Response::Error("No idea how you were able to trigger this.".to_string()));
2761 continue 'background_loop;
2762 }
2763
2764 }
2765 ContainerPath::File(path) => {
2766 let table_name = path.split('/').collect::<Vec<_>>()[1];
2767 match dependencies.import_from_ak(table_name, schema) {
2768 Ok(table) => {
2769 let file_path = if table_name.starts_with("ceo") {
2770 format!("ceo_{}", path)
2771 } else {
2772 path.clone()
2773 };
2774
2775 let file = RFile::new_from_decoded(&RFileDecoded::DB(table), 0, &file_path);
2776 files.push(file);
2777 },
2778 Err(_) => not_added_paths.push(path.clone()),
2779 }
2780 }
2781 }
2782 }
2783
2784 for file in files {
2785 if let Ok(Some(path)) = pack.insert(file) {
2786 added_paths.push(path);
2787 }
2788 }
2789 },
2790 None => {
2791 CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string()));
2792 continue 'background_loop;
2793 }
2794 }
2795 },
2796 _ => {
2797 CentralCommand::send_back(&sender, Response::Error("You can't import files from this source.".to_string()));
2798 continue 'background_loop;
2799 },
2800 }
2801 }
2802
2803 CentralCommand::send_back(&sender, Response::VecContainerPathVecString(added_paths, not_added_paths));
2804 }
2805 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2806 }
2807 },
2808
2809 Command::GetRFilesFromAllSources(paths, force_lowercased_paths) => {
2810 let mut packed_files = HashMap::new();
2811 let dependencies = dependencies.read().unwrap();
2812
2813 let mut packed_files_parent = HashMap::new();
2815 for (path, file) in dependencies.files_by_path(&paths, false, true, true) {
2816 packed_files_parent.insert(if force_lowercased_paths { path.to_lowercase() } else { path }, file.clone());
2817 }
2818
2819 let mut packed_files_game = HashMap::new();
2821 for (path, file) in dependencies.files_by_path(&paths, true, false, true) {
2822 packed_files_game.insert(if force_lowercased_paths { path.to_lowercase() } else { path }, file.clone());
2823 }
2824
2825 let mut packed_files_packfile = HashMap::new();
2836 for pack in packs.values() {
2837 for file in pack.files_by_paths(&paths, true) {
2838 packed_files_packfile.insert(if force_lowercased_paths { file.path_in_container_raw().to_lowercase() } else { file.path_in_container_raw().to_owned() }, file.clone());
2839 }
2840 }
2841
2842 packed_files.insert(DataSource::ParentFiles, packed_files_parent);
2843 packed_files.insert(DataSource::GameFiles, packed_files_game);
2844 packed_files.insert(DataSource::PackFile, packed_files_packfile);
2845
2846 CentralCommand::send_back(&sender, Response::HashMapDataSourceHashMapStringRFile(packed_files));
2848 },
2849
2850 Command::GetAnimPathsBySkeletonName(skeleton_name) => {
2851 let mut paths = HashSet::new();
2852 let mut dependencies = dependencies.write().unwrap();
2853
2854 let mut packed_files_parent = HashSet::new();
2856 for (path, file) in dependencies.files_by_types_mut(&[FileType::Anim], false, true) {
2857 if let Ok(Some(RFileDecoded::Anim(file))) = file.decode(&None, false, true) {
2858 if file.skeleton_name() == &skeleton_name {
2859 packed_files_parent.insert(path);
2860 }
2861 }
2862 }
2863
2864 let mut packed_files_game = HashSet::new();
2866 for (path, file) in dependencies.files_by_types_mut(&[FileType::Anim], true, false) {
2867 if let Ok(Some(RFileDecoded::Anim(file))) = file.decode(&None, false, true) {
2868 if file.skeleton_name() == &skeleton_name {
2869 packed_files_game.insert(path);
2870 }
2871 }
2872 }
2873
2874 let mut packed_files_packfile = HashSet::new();
2876 for pack in packs.values_mut() {
2877 for file in pack.files_by_type_mut(&[FileType::Anim]) {
2878 if let Ok(Some(RFileDecoded::Anim(anim_file))) = file.decode(&None, false, true) {
2879 if anim_file.skeleton_name() == &skeleton_name {
2880 packed_files_packfile.insert(file.path_in_container_raw().to_owned());
2881 }
2882 }
2883 }
2884 }
2885
2886 paths.extend(packed_files_game);
2887 paths.extend(packed_files_parent);
2888 paths.extend(packed_files_packfile);
2889
2890 CentralCommand::send_back(&sender, Response::HashSetString(paths));
2892 },
2893
2894 Command::GetPackedFilesNamesStartingWitPathFromAllSources(path) => {
2895 let mut files: HashMap<DataSource, HashSet<ContainerPath>> = HashMap::new();
2896 let dependencies = dependencies.read().unwrap();
2897
2898 let parent_files = dependencies.files_by_path(std::slice::from_ref(&path), false, true, true);
2899 if !parent_files.is_empty() {
2900 files.insert(DataSource::ParentFiles, parent_files.into_keys().map(ContainerPath::File).collect());
2901 }
2902
2903 let game_files = dependencies.files_by_path(std::slice::from_ref(&path), true, false, true);
2904 if !game_files.is_empty() {
2905 files.insert(DataSource::GameFiles, game_files.into_keys().map(ContainerPath::File).collect());
2906 }
2907
2908 let mut local_file_paths = HashSet::new();
2909 for pack in packs.values() {
2910 for file in pack.files_by_path(&path, true) {
2911 local_file_paths.insert(file.path_in_container());
2912 }
2913 }
2914 if !local_file_paths.is_empty() {
2915 files.insert(DataSource::PackFile, local_file_paths);
2916 }
2917
2918 CentralCommand::send_back(&sender, Response::HashMapDataSourceHashSetContainerPath(files));
2920 },
2921
2922 Command::SavePackedFilesToPackFileAndClean(pack_key, files, optimize) => {
2923 match packs.get_mut(&pack_key) {
2924 Some(pack) => {
2925 match &schema {
2926 Some(ref schema) => {
2927
2928 let mut added_paths = vec![];
2931 for file in files {
2932 if let Ok(Some(path)) = pack.insert(file) {
2933 added_paths.push(path);
2934 }
2935 }
2936
2937 added_paths.sort();
2939 added_paths.dedup();
2940
2941 if optimize {
2942
2943 let options = settings.optimizer_options();
2945
2946 match pack.optimize(None, &mut dependencies.write().unwrap(), schema, game, &options) {
2948 Ok((paths_to_delete, paths_to_add)) => {
2949 added_paths.extend(paths_to_add.into_iter()
2950 .map(ContainerPath::File)
2951 .collect::<Vec<_>>());
2952 CentralCommand::send_back(&sender, Response::VecContainerPathVecContainerPath(added_paths, paths_to_delete.into_iter()
2953 .map(ContainerPath::File)
2954 .collect()));
2955 },
2956 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2957 }
2958 } else {
2959 CentralCommand::send_back(&sender, Response::VecContainerPathVecContainerPath(added_paths, vec![]));
2960 }
2961 },
2962 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string())),
2963 }
2964 }
2965 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2966 }
2967 },
2968
2969 Command::NotesForPath(pack_key, path) => {
2970 match packs.get(&pack_key) {
2971 Some(pack) => CentralCommand::send_back(&sender, Response::VecNote(pack.notes().notes_by_path(&path))),
2972 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2973 }
2974 }
2975 Command::AddNote(pack_key, note) => {
2976 match packs.get_mut(&pack_key) {
2977 Some(pack) => CentralCommand::send_back(&sender, Response::Note(pack.notes_mut().add_note(note))),
2978 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2979 }
2980 }
2981 Command::DeleteNote(pack_key, path, id) => {
2982 match packs.get_mut(&pack_key) {
2983 Some(pack) => {
2984 pack.notes_mut().delete_note(&path, id);
2985 CentralCommand::send_back(&sender, Response::Success);
2986 }
2987 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
2988 }
2989 }
2990
2991 Command::SaveLocalSchemaPatch(patches) => {
2992 let path = table_patches_path().unwrap().join(game.schema_file_name());
2993 match Schema::save_patches(&patches, &path) {
2994 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
2995 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
2996 }
2997 }
2998 Command::RemoveLocalSchemaPatchesForTable(table_name) => {
2999 let path = table_patches_path().unwrap().join(game.schema_file_name());
3000 match Schema::remove_patches_for_table(&table_name, &path) {
3001 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3002 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3003 }
3004 }
3005 Command::RemoveLocalSchemaPatchesForTableAndField(table_name, field_name) => {
3006 let path = table_patches_path().unwrap().join(game.schema_file_name());
3007 match Schema::remove_patches_for_table_and_field(&table_name, &field_name, &path) {
3008 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3009 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3010 }
3011 }
3012 Command::ImportSchemaPatch(patch) => {
3013 match schema {
3014 Some(ref mut schema) => {
3015 Schema::add_patches_to_patch_set(schema.patches_mut(), &patch);
3016 match schema.save(&schemas_path().unwrap().join(game.schema_file_name())) {
3017 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3018 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3019 }
3020 }
3021 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string())),
3022 }
3023 }
3024
3025 Command::GenerateMissingLocData(_pack_key) => {
3026 match dependencies.read().unwrap().generate_missing_loc_data(&mut packs) {
3027 Ok(path) => CentralCommand::send_back(&sender, Response::VecContainerPath(path)),
3028 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3029 }
3030 }
3031
3032 Command::PackMap(pack_key, tile_maps, tiles) => {
3033 match schema {
3034 Some(ref schema) => {
3035 let mut dependencies = dependencies.write().unwrap();
3036 let options = settings.optimizer_options();
3037 match dependencies.add_tile_maps_and_tiles(&mut packs, Some(&pack_key), game, schema, options, tile_maps, tiles) {
3038 Ok((paths_to_add, paths_to_delete)) => CentralCommand::send_back(&sender, Response::VecContainerPathVecContainerPath(paths_to_add, paths_to_delete)),
3039 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3040 }
3041 }
3042 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string())),
3043 }
3044 }
3045
3046 Command::InitializeMyModFolder(mod_name, mod_game, sublime_support, vscode_support, git_support) => {
3048 let mut mymod_path = settings.path_buf(MYMOD_BASE_PATH);
3049 if !mymod_path.is_dir() {
3050 CentralCommand::send_back(&sender, Response::Error("MyMod path is not configured. Configure it in the settings and try again.".to_string()));
3051 continue;
3052 }
3053
3054 mymod_path.push(&mod_game);
3055
3056 if let Err(error) = DirBuilder::new().recursive(true).create(&mymod_path) {
3058 CentralCommand::send_back(&sender, Response::Error(format!("Error while creating the MyMod's Game folder: {}.", error)));
3059 continue;
3060 }
3061
3062 mymod_path.push(&mod_name);
3064 if let Err(error) = DirBuilder::new().recursive(true).create(&mymod_path) {
3065 CentralCommand::send_back(&sender, Response::Error(format!("Error while creating the MyMod's Assets folder: {}.", error)));
3066 continue;
3067 };
3068
3069 if let Some(gitignore) = git_support {
3071 let git_integration = GitIntegration::new(&mymod_path, "", "", "");
3072 if let Err(error) = git_integration.init() {
3073 CentralCommand::send_back(&sender, Response::Error(error.to_string()));
3074 continue
3075 }
3076
3077 if let Err(error) = git_integration.add_gitignore(&gitignore) {
3078 CentralCommand::send_back(&sender, Response::Error(error.to_string()));
3079 continue
3080 }
3081 }
3082
3083 if sublime_support || vscode_support {
3085 if let Ok(lua_autogen_folder) = lua_autogen_game_path(game) {
3086 let lua_autogen_folder = lua_autogen_folder.to_string_lossy().to_string().replace('\\', "/");
3087
3088 if vscode_support {
3090 let mut vscode_config_path = mymod_path.to_owned();
3091 vscode_config_path.push(".vscode");
3092
3093 if let Err(error) = DirBuilder::new().recursive(true).create(&vscode_config_path) {
3094 CentralCommand::send_back(&sender, Response::Error(format!("Error while creating the VSCode Config folder: {}.", error)));
3095 continue;
3096 };
3097
3098 let mut vscode_extensions_path_file = vscode_config_path.to_owned();
3099 vscode_extensions_path_file.push("extensions.json");
3100 if let Ok(file) = File::create(vscode_extensions_path_file) {
3101 let mut file = BufWriter::new(file);
3102 let _ = file.write_all("
3103{
3104 \"recommendations\": [
3105 \"sumneko.lua\",
3106 \"formulahendry.code-runner\"
3107 ],
3108}".as_bytes());
3109 }
3110 }
3111
3112 if sublime_support {
3114 let mut sublime_config_path = mymod_path.to_owned();
3115 sublime_config_path.push(format!("{mod_name}.sublime-project"));
3116 if let Ok(file) = File::create(sublime_config_path) {
3117 let mut file = BufWriter::new(file);
3118 let _ = file.write_all("
3119{
3120 \"folders\":
3121 [
3122 {
3123 \"path\": \".\"
3124 }
3125 ]
3126}".to_string().as_bytes());
3127 }
3128 }
3129
3130 let mut luarc_config_path = mymod_path.to_owned();
3132 luarc_config_path.push(".luarc.json");
3133
3134 if let Ok(file) = File::create(luarc_config_path) {
3135 let mut file = BufWriter::new(file);
3136 let _ = file.write_all(format!("
3137{{
3138 \"workspace.library\": [
3139 \"{lua_autogen_folder}/global/\",
3140 \"{lua_autogen_folder}/campaign/\",
3141 \"{lua_autogen_folder}/frontend/\",
3142 \"{lua_autogen_folder}/battle/\"
3143 ],
3144 \"runtime.version\": \"Lua 5.1\",
3145 \"completion.autoRequire\": false,
3146 \"workspace.preloadFileSize\": 1500,
3147 \"workspace.ignoreSubmodules\": false,
3148 \"diagnostics.workspaceDelay\": 500,
3149 \"diagnostics.workspaceRate\": 40,
3150 \"diagnostics.disable\": [
3151 \"lowercase-global\",
3152 \"trailing-space\"
3153 ],
3154 \"hint.setType\": true,
3155 \"workspace.ignoreDir\": [
3156 \".vscode\",
3157 \".git\"
3158 ]
3159}}").as_bytes());
3160 }
3161 }
3162 }
3163
3164 mymod_path.set_extension("pack");
3166 CentralCommand::send_back(&sender, Response::PathBuf(mymod_path));
3167 },
3168
3169 Command::LiveExport(pack_key) => {
3170 match packs.get_mut(&pack_key) {
3171 Some(pack) => {
3172 let game_path = settings.path_buf(game.key());
3173 let disable_regen_table_guid = settings.bool("disable_uuid_regeneration_on_db_tables");
3174 let keys_first = settings.bool("tables_use_old_column_order_for_tsv");
3175 match pack.live_export(game, &game_path, disable_regen_table_guid, keys_first) {
3176 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3177 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3178 }
3179 }
3180 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
3181 }
3182 },
3183
3184 Command::SetPackOperationalMode(pack_key, mode) => {
3185 if packs.contains_key(&pack_key) {
3186 pack_modes.insert(pack_key, mode);
3187 CentralCommand::send_back(&sender, Response::Success);
3188 } else {
3189 CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key)));
3190 }
3191 },
3192
3193 Command::GetPackOperationalMode(pack_key) => {
3194 let mode = pack_modes.get(&pack_key).cloned().unwrap_or(OperationalMode::Normal);
3195 CentralCommand::send_back(&sender, Response::OperationalMode(mode));
3196 },
3197
3198 Command::AddLineToPackIgnoredDiagnostics(pack_key, line) => {
3199 match packs.get_mut(&pack_key) {
3200 Some(pack) => {
3201 if let Some(diagnostics_ignored) = pack.settings_mut().settings_text_mut().get_mut("diagnostics_files_to_ignore") {
3202 diagnostics_ignored.push_str(&line);
3203 } else {
3204 pack.settings_mut().settings_text_mut().insert("diagnostics_files_to_ignore".to_owned(), line);
3205 }
3206 CentralCommand::send_back(&sender, Response::Success);
3207 }
3208 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
3209 }
3210 },
3211
3212 Command::UpdateEmpireAndNapoleonAK => {
3213 let sender = sender.clone();
3214 tokio::spawn(async move {
3215 let result = tokio::task::spawn_blocking(|| {
3216 match old_ak_files_path() {
3217 Ok(local_path) => {
3218 let git_integration = GitIntegration::new(&local_path, OLD_AK_REPO, OLD_AK_BRANCH, OLD_AK_REMOTE);
3219 git_integration.update_repo().map(|_| ()).map_err(|e| e.into())
3220 },
3221 Err(error) => Err(error),
3222 }
3223 }).await.unwrap();
3224
3225 match result {
3226 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3227 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3228 }
3229 });
3230 }
3231
3232 Command::GetPackTranslation(pack_key, language) => {
3233 let game_key = game.key();
3234 match translations_local_path() {
3235 Ok(local_path) => {
3236 let mut base_english = HashMap::new();
3237 let mut base_local_fixes = HashMap::new();
3238
3239 match translations_remote_path() {
3240 Ok(remote_path) => {
3241
3242 let vanilla_loc_path = remote_path.join(format!("{}/{}", game.key(), VANILLA_LOC_NAME));
3243 if let Ok(mut vanilla_loc) = RFile::tsv_import_from_path(&vanilla_loc_path, &None) {
3244 let _ = vanilla_loc.guess_file_type();
3245 if let Ok(RFileDecoded::Loc(vanilla_loc)) = vanilla_loc.decoded() {
3246
3247 let fixes_loc_path = remote_path.join(format!("{}/{}{}.tsv", game.key(), VANILLA_FIXES_NAME, language));
3249 if let Ok(mut fixes_loc) = RFile::tsv_import_from_path(&fixes_loc_path, &None) {
3250 let _ = fixes_loc.guess_file_type();
3251
3252 if let Ok(RFileDecoded::Loc(fixes_loc)) = fixes_loc.decoded() {
3253 base_local_fixes.extend(fixes_loc.data().iter().map(|x| (x[0].data_to_string().to_string(), x[1].data_to_string().to_string())).collect::<Vec<_>>());
3254 }
3255 }
3256
3257 base_english.extend(vanilla_loc.data().iter().map(|x| (x[0].data_to_string().to_string(), x[1].data_to_string().to_string())).collect::<Vec<_>>());
3258 }
3259 }
3260
3261 let dependencies = dependencies.read().unwrap();
3262 let paths = vec![local_path, remote_path];
3263 let Some(pack_ref) = get_pack(&packs, &pack_key, &sender) else { continue 'background_loop; };
3264 match PackTranslation::new(&paths, pack_ref, game_key, &language, &dependencies, &base_english, &base_local_fixes) {
3265 Ok(tr) => CentralCommand::send_back(&sender, Response::PackTranslation(tr)),
3266 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3267 }
3268 }
3269 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3270 }
3271 },
3272 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3273 }
3274 }
3275
3276 Command::UpdateTranslations => {
3277 let sender = sender.clone();
3278 tokio::spawn(async move {
3279 let result = tokio::task::spawn_blocking(|| {
3280 match translations_remote_path() {
3281 Ok(local_path) => {
3282 let git_integration = GitIntegration::new(&local_path, TRANSLATIONS_REPO, TRANSLATIONS_BRANCH, TRANSLATIONS_REMOTE);
3283 git_integration.update_repo().map(|_| ()).map_err(|e| e.into())
3284 },
3285 Err(error) => Err(error),
3286 }
3287 }).await.unwrap();
3288
3289 match result {
3290 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3291 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3292 }
3293 });
3294 }
3295
3296 Command::BuildStarposGetCampaingIds(_pack_key) => {
3297 let ids = dependencies.read().unwrap().db_values_from_table_name_and_column_name(Some(&packs), "campaigns_tables", "campaign_name", true, true);
3298 CentralCommand::send_back(&sender, Response::HashSetString(ids));
3299 }
3300
3301 Command::BuildStarposCheckVictoryConditions(pack_key) => {
3302 let Some(pack_ref) = get_pack(&packs, &pack_key, &sender) else { continue 'background_loop; };
3303 if !GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) || (
3304 GAMES_NEEDING_VICTORY_OBJECTIVES.contains(&game.key()) &&
3305 pack_ref.file(VICTORY_OBJECTIVES_FILE_NAME, false).is_some()
3306 ) {
3307 CentralCommand::send_back(&sender, Response::Success);
3308 } else {
3309 CentralCommand::send_back(&sender, Response::Error("Missing \"db/victory_objectives.txt\" file. Processing the startpos without this file will result in issues in campaign. Add the file to the pack and try again.".to_string()));
3310 }
3311 }
3312
3313 Command::BuildStarpos(pack_key, campaign_id, process_hlp_spd_data) => {
3314 let dependencies = dependencies.read().unwrap();
3315 let game_path = settings.path_buf(game.key());
3316
3317 if game.key() == KEY_THREE_KINGDOMS {
3319 match dependencies.build_starpos_pre(&mut packs, Some(&pack_key), game, &game_path, &campaign_id, process_hlp_spd_data, "historical") {
3320 Ok(_) => match dependencies.build_starpos_pre(&mut packs, Some(&pack_key), game, &game_path, &campaign_id, false, "romance") {
3321 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3322 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3323 }
3324 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3325 }
3326 } else {
3327 match dependencies.build_starpos_pre(&mut packs, Some(&pack_key), game, &game_path, &campaign_id, process_hlp_spd_data, "") {
3328 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3329 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3330 }
3331 }
3332 }
3333
3334 Command::BuildStarposPost(pack_key, campaign_id, process_hlp_spd_data) => {
3335 let dependencies = dependencies.read().unwrap();
3336 let game_path = settings.path_buf(game.key());
3337 let asskit_path = Some(settings.path_buf(&(game.key().to_owned() + ASSEMBLY_KIT_SUFFIX)));
3338
3339 let sub_start_pos = if game.key() == KEY_THREE_KINGDOMS {
3340 vec!["historical".to_owned(), "romance".to_owned()]
3341 } else {
3342 vec![]
3343 };
3344
3345 match dependencies.build_starpos_post(&mut packs, Some(&pack_key), game, &game_path, asskit_path, &campaign_id, process_hlp_spd_data, false, &sub_start_pos) {
3346 Ok(paths) => CentralCommand::send_back(&sender, Response::VecContainerPath(paths)),
3347 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3348 }
3349 },
3350
3351 Command::BuildStarposCleanup(pack_key, campaign_id, process_hlp_spd_data) => {
3352 let dependencies = dependencies.read().unwrap();
3353 let game_path = settings.path_buf(game.key());
3354 let asskit_path = Some(settings.path_buf(&(game.key().to_owned() + ASSEMBLY_KIT_SUFFIX)));
3355
3356 let sub_start_pos = if game.key() == KEY_THREE_KINGDOMS {
3357 vec!["historical".to_owned(), "romance".to_owned()]
3358 } else {
3359 vec![]
3360 };
3361
3362 match dependencies.build_starpos_post(&mut packs, Some(&pack_key), game, &game_path, asskit_path, &campaign_id, process_hlp_spd_data, true, &sub_start_pos) {
3363 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3364 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3365 }
3366 },
3367
3368 Command::BuildCeo(pack_key, akit_path, bob_exe_path) => {
3369 use std::process::{Command as SysCommand, Stdio};
3370 use std::time::Instant;
3371
3372 info!("[BuildCeo] handler entered: pack={pack_key}, akit={akit_path}, bob={bob_exe_path}");
3373 let akit_root = PathBuf::from(&akit_path);
3374 let bob_exe = PathBuf::from(&bob_exe_path);
3375 let bob_dir = match bob_exe.parent() {
3376 Some(d) => d.to_path_buf(),
3377 None => { CentralCommand::send_back(&sender, Response::Error("Invalid BOB path".into())); continue 'background_loop; }
3378 };
3379 let raw_db = akit_root.join(r"raw_data\db");
3380 let ceo_ccd = akit_root.join(r"working_data\campaigns\ceo_data.ccd");
3381
3382 if ceo_ccd.exists() {
3384 let bak = ceo_ccd.with_extension("ccd.bak1");
3385 if let Err(e) = std::fs::copy(&ceo_ccd, &bak) {
3386 CentralCommand::send_back(&sender, Response::Error(format!("Failed to backup ceo_data.ccd: {e}")));
3387 continue 'background_loop;
3388 }
3389 }
3390
3391 let mut xml_backups: Vec<(PathBuf, PathBuf)> = Vec::new();
3393 if raw_db.exists() {
3394 match std::fs::read_dir(&raw_db) {
3395 Ok(entries) => {
3396 for entry in entries.filter_map(|e| e.ok()) {
3397 let fname = entry.file_name();
3398 let s = fname.to_string_lossy().to_lowercase();
3399 if s.starts_with("ceo") && s.ends_with(".xml") {
3400 let orig = entry.path();
3401 let bak = orig.with_extension("xml.bak");
3402 if std::fs::copy(&orig, &bak).is_ok() {
3403 xml_backups.push((orig, bak));
3404 }
3405 }
3406 }
3407 }
3408 Err(e) => {
3409 CentralCommand::send_back(&sender, Response::Error(format!("Failed to read raw_data/db: {e}")));
3410 continue 'background_loop;
3411 }
3412 }
3413 }
3414
3415 let pack_ref = match packs.get_mut(&pack_key) {
3417 Some(p) => p,
3418 None => {
3419 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3420 CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {pack_key}")));
3421 continue 'background_loop;
3422 }
3423 };
3424
3425 let ceo_allowed_folders: std::collections::HashSet<&str> = [
3427 "ceo_active_permissions_tables",
3428 "ceo_anti_ceo_pairs_tables",
3429 "ceo_can_equip_requirements_tables",
3430 "ceo_categories_tables",
3431 "ceo_effect_list_to_effects_tables",
3432 "ceo_effect_lists_tables",
3433 "ceo_equipment_category_managers_tables",
3434 "ceo_equipment_manager_all_possible_ceos_tables",
3435 "ceo_equipment_manager_campaign_lookups_tables",
3436 "ceo_equipment_manager_to_category_managers_tables",
3437 "ceo_equipment_manager_types_tables",
3438 "ceo_equipment_managers_tables",
3439 "ceo_equipped_set_bonus_ceos_tables",
3440 "ceo_equipped_set_bonus_effect_bundles_tables",
3441 "ceo_equipped_set_bonuses_tables",
3442 "ceo_equipped_set_bonuses_to_incident_junctions_tables",
3443 "ceo_event_feed_categories_tables",
3444 "ceo_group_ceos_tables",
3445 "ceo_group_spawners_tables",
3446 "ceo_groups_tables",
3447 "ceo_initial_data_active_ceos_tables",
3448 "ceo_initial_data_active_spawners_tables",
3449 "ceo_initial_data_equipments_tables",
3450 "ceo_initial_data_scripted_permissions_tables",
3451 "ceo_initial_data_stages_tables",
3452 "ceo_initial_data_to_stages_tables",
3453 "ceo_initial_data_triggers_tables",
3454 "ceo_initial_datas_tables",
3455 "ceo_location_enums_tables",
3456 "ceo_nodes_tables",
3457 "ceo_permissions_groups_tables",
3458 "ceo_permissions_tables",
3459 "ceo_post_battle_loot_chances_tables",
3460 "ceo_rarities_tables",
3461 "ceo_scripted_permissions_tables",
3462 "ceo_scripted_permissions_to_permissions_tables",
3463 "ceo_set_items_tables",
3464 "ceo_sets_tables",
3465 "ceo_spawner_can_spawn_requirements_tables",
3466 "ceo_spawners_tables",
3467 "ceo_template_manager_all_possible_ceos_tables",
3468 "ceo_template_manager_campaign_lookups_tables",
3469 "ceo_template_manager_ceo_limits_tables",
3470 "ceo_template_manager_ceo_spawn_limits_tables",
3471 "ceo_template_manager_supported_categories_tables",
3472 "ceo_template_manager_types_tables",
3473 "ceo_template_managers_tables",
3474 "ceo_threshold_nodes_tables",
3475 "ceo_thresholds_tables",
3476 "ceo_to_target_ceo_junctions_tables",
3477 "ceo_to_target_factions_tables",
3478 "ceo_to_target_junction_reasons_tables",
3479 "ceo_to_target_province_junctions_tables",
3480 "ceo_to_ui_display_junctions_tables",
3481 "ceo_trigger_behaviour_enums_tables",
3482 "ceo_trigger_target_requirements_tables",
3483 "ceo_trigger_targets_tables",
3484 "ceo_trigger_to_trigger_targets_tables",
3485 "ceo_triggers_tables",
3486 "ceos_tables",
3487 "ceos_to_equipment_variants_tables",
3488 ].iter().copied().collect();
3489
3490 let ceo_table_paths: Vec<String> = pack_ref.files()
3491 .keys()
3492 .filter(|p| {
3493 let mut parts = p.splitn(3, '/');
3494 let prefix = parts.next().unwrap_or("");
3495 if prefix != "db" && prefix != "ceo_db" {
3496 return false;
3497 }
3498 parts.next()
3499 .map(|folder| ceo_allowed_folders.contains(folder))
3500 .unwrap_or(false)
3501 })
3502 .cloned()
3503 .collect();
3504
3505 if ceo_table_paths.is_empty() {
3507 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3508 CentralCommand::send_back(&sender, Response::Error(
3509 "No CEO tables found in the pack (looked in db/ and ceo_db/ folders). \
3510 Import CEO tables from the Assembly Kit.".into()
3511 ));
3512 continue 'background_loop;
3513 }
3514
3515 let required_tables = [
3517 "ceos_tables",
3518 "ceo_nodes_tables",
3519 "ceo_thresholds_tables",
3520 "ceo_threshold_nodes_tables",
3521 "ceo_initial_datas_tables",
3522 ];
3523 let present_folders: std::collections::HashSet<&str> = ceo_table_paths.iter()
3524 .filter_map(|p| p.split('/').nth(1))
3525 .collect();
3526 let missing: Vec<&&str> = required_tables.iter()
3527 .filter(|t| !present_folders.contains(**t))
3528 .collect();
3529 if !missing.is_empty() {
3530 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3531 let missing_list = missing.iter().map(|t| format!(" - {}", t)).collect::<Vec<_>>().join("\n");
3532 CentralCommand::send_back(&sender, Response::Error(
3533 format!("The following required CEO tables are missing from the pack:\n{}\n\n\
3534 Import them from the Assembly Kit generate them.", missing_list)
3535 ));
3536 continue 'background_loop;
3537 }
3538
3539 let decode_extra = {
3540 let mut d = DecodeableExtraData::default();
3541 d.set_schema(schema.as_ref());
3542 Some(d)
3543 };
3544 let mut export_errors: Vec<String> = Vec::new();
3545
3546 let mut xml_groups: std::collections::BTreeMap<String, Vec<String>> = std::collections::BTreeMap::new();
3549 for table_path in &ceo_table_paths {
3550 let parts: Vec<&str> = table_path.split('/').collect();
3552 if parts.len() < 2 { continue; }
3553 let folder = parts[1];
3554 let xml_name = if let Some(folder) = folder.strip_suffix("_tables") {
3555 folder.to_owned() + ".xml"
3556 } else {
3557 folder.to_owned() + ".xml"
3558 };
3559 xml_groups.entry(xml_name).or_default().push(table_path.clone());
3560 }
3561
3562 for (xml_name, table_paths) in &xml_groups {
3563 let xml_path = raw_db.join(xml_name);
3564 let table_tag = xml_name.trim_end_matches(".xml");
3565 let xsd_name = xml_name.replace(".xml", ".xsd");
3566
3567 let mut xml = format!(
3568 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n\
3569 <dataroot xmlns:od=\"urn:schemas-microsoft-com:officedata\" \
3570 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \
3571 xsi:noNamespaceSchemaLocation=\"{xsd_name}\" \
3572 export_time=\"\" revision=\"0\" export_branch=\"\" export_user=\"rpfm\">\r\n\
3573 <edit_uuid>00000000-0000-0000-0000-000000000000</edit_uuid>\r\n"
3574 );
3575
3576 for table_path in table_paths {
3577 let rfile = match pack_ref.files_mut().get_mut(table_path.as_str()) {
3578 Some(f) => f,
3579 None => continue,
3580 };
3581
3582 let _ = rfile.load();
3583 let _ = rfile.decode(&decode_extra, true, false);
3584
3585 let db_table = match rfile.decoded() {
3586 Ok(RFileDecoded::DB(db)) => db.clone(),
3587 _ => { export_errors.push(format!("Could not decode {table_path}")); continue; }
3588 };
3589
3590 let fields: Vec<_> = db_table.definition().fields_processed().to_vec();
3591
3592 for row in db_table.data().iter() {
3593 let mut field_pairs: Vec<(String, String)> = Vec::new();
3594 for (field_def, value) in fields.iter().zip(row.iter()) {
3595 let fname = field_def.name().to_owned();
3596 let val_str = match value {
3597 DecodedData::Boolean(b) => if *b { "1".to_owned() } else { "0".to_owned() },
3598 DecodedData::I16(v) => v.to_string(),
3599 DecodedData::I32(v) => v.to_string(),
3600 DecodedData::I64(v) => v.to_string(),
3601 DecodedData::OptionalI16(v) => v.to_string(),
3602 DecodedData::OptionalI32(v) => v.to_string(),
3603 DecodedData::OptionalI64(v) => v.to_string(),
3604 DecodedData::F32(v) => v.to_string(),
3605 DecodedData::F64(v) => v.to_string(),
3606 DecodedData::StringU8(s) | DecodedData::StringU16(s) |
3607 DecodedData::OptionalStringU8(s) | DecodedData::OptionalStringU16(s) => s.clone(),
3608 DecodedData::ColourRGB(s) => s.clone(),
3609 _ => String::new(),
3610 };
3611 let escaped = val_str
3612 .replace('&', "&")
3613 .replace('<', "<")
3614 .replace('>', ">")
3615 .replace('"', """);
3616 field_pairs.push((fname, escaped));
3617 }
3618 field_pairs.sort_by(|a, b| a.0.cmp(&b.0));
3619
3620 xml.push_str(&format!("<{table_tag}>\r\n"));
3621 for (fname, val) in &field_pairs {
3622 xml.push_str(&format!("<{fname}>{val}</{fname}>\r\n"));
3623 }
3624 xml.push_str(&format!("</{table_tag}>\r\n"));
3625 }
3626 }
3627
3628 xml.push_str("</dataroot>\r\n");
3629
3630 if let Err(e) = std::fs::write(&xml_path, xml.as_bytes()) {
3631 export_errors.push(format!("Failed to write {}: {e}", xml_path.display()));
3632 }
3633 }
3634
3635 if !export_errors.is_empty() {
3636 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3637 CentralCommand::send_back(&sender, Response::Error(format!("Export errors:\n{}", export_errors.join("\n"))));
3638 continue 'background_loop;
3639 }
3640
3641 let cfg_path = bob_dir.join("BOB/default_configuration.xml");
3643
3644 if let Some(cfg_dir) = cfg_path.parent() {
3647 if let Err(e) = std::fs::create_dir_all(cfg_dir) {
3648 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3649 CentralCommand::send_back(&sender, Response::Error(format!(
3650 "Failed to create BOB config directory {}: {e}", cfg_dir.display())));
3651 continue 'background_loop;
3652 }
3653 }
3654
3655 let cfg_backup = bob_dir.join("BOB/default_configuration.xml.rpfm_bak");
3657 let cfg_existed = cfg_path.exists();
3658 if cfg_existed {
3659 if let Err(e) = std::fs::rename(&cfg_path, &cfg_backup) {
3660 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3661 CentralCommand::send_back(&sender, Response::Error(format!(
3662 "Failed to backup BOB config {}: {e}", cfg_path.display())));
3663 continue 'background_loop;
3664 }
3665 }
3666
3667 const BOB_CONFIG_XML: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
3668 <bob_configuration><processors><processor>Campaign</processor></processors>
3669 <directories/><global_rules/><retail>0</retail><silent>1</silent>
3670 <get_latest>0</get_latest><connect_db>0</connect_db>
3671 <merge_for_checkin_mode>2</merge_for_checkin_mode>
3672 <selected_files><entry><working>/campaigns/ceo_data.ccd</entry></selected_files>
3673 </bob_configuration>"#;
3674
3675 if let Err(e) = std::fs::write(&cfg_path, BOB_CONFIG_XML) {
3676 if cfg_existed { let _ = std::fs::rename(&cfg_backup, &cfg_path); }
3678 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3679 CentralCommand::send_back(&sender, Response::Error(format!(
3680 "Failed to write BOB config {}: {e}", cfg_path.display())));
3681 continue 'background_loop;
3682 }
3683
3684 info!("[BuildCeo] launching BOB: {} (cwd {})", bob_exe.display(), bob_dir.display());
3685 let bob_start = Instant::now();
3686
3687 let mut cmd = SysCommand::new(&bob_exe);
3690 cmd.current_dir(&bob_dir).stdin(Stdio::null());
3691
3692 let status = match cmd.status() {
3693 Ok(s) => s,
3694 Err(e) => {
3695 let _ = std::fs::remove_file(&cfg_path);
3696 if cfg_existed { let _ = std::fs::rename(&cfg_backup, &cfg_path); }
3697 for (orig, bak) in &xml_backups { let _ = std::fs::rename(bak, orig); }
3698 info!("[BuildCeo] BOB spawn failed after {:?}: {e}", bob_start.elapsed());
3699 CentralCommand::send_back(&sender, Response::Error(format!("Failed to launch BOB: {e}")));
3700 continue 'background_loop;
3701 }
3702 };
3703
3704 info!("[BuildCeo] BOB exited after {:?}, exit={:?}", bob_start.elapsed(), status.code());
3705
3706 let _ = std::fs::remove_file(&cfg_path);
3708 if cfg_existed { let _ = std::fs::rename(&cfg_backup, &cfg_path); }
3709
3710 let found = ceo_ccd.exists();
3714 info!("[BuildCeo] ceo_data.ccd present: {found}");
3715
3716 for (orig, bak) in &xml_backups {
3718 let _ = std::fs::rename(bak, orig);
3719 }
3720
3721 if found {
3723 CentralCommand::send_back(&sender, Response::Success);
3724 } else {
3725 CentralCommand::send_back(&sender, Response::Error(format!(
3726 "BOB finished (exit {:?}) but ceo_data.ccd was not generated. \
3727 This usually means BOB hit an error in the exported tables.",
3728 status.code()
3729 )));
3730 }
3731 }
3732
3733 Command::BuildCeoPost(pack_key, akit_path) => {
3734 match packs.get_mut(&pack_key) {
3735 Some(pack) => {
3736 match build_ceo_post(pack, &akit_path) {
3737 Ok(paths) => CentralCommand::send_back(&sender, Response::VecContainerPath(paths)),
3738 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3739 }
3740 }
3741 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {pack_key}"))),
3742 }
3743 }
3744
3745 Command::GetTraitCeos => {
3746 let deps = dependencies.read().unwrap();
3747 let trait_ceos = get_trait_ceos(&deps);
3748 CentralCommand::send_back(&sender, Response::VecStringTuples(trait_ceos));
3749 }
3750
3751 Command::BuildCeoEntries(pack_key, entries) => {
3752 let result = (|| -> Result<Vec<ContainerPath>> {
3753 let schema = schema.as_ref()
3754 .ok_or_else(|| anyhow!("No schema loaded for the current game."))?;
3755 let pack = packs.get_mut(&pack_key)
3756 .ok_or_else(|| anyhow!("Pack not found: {}", pack_key))?;
3757 build_ceo_entries(pack, schema, &entries)
3758 })();
3759
3760 match result {
3761 Ok(paths) => CentralCommand::send_back(&sender, Response::VecContainerPath(paths)),
3762 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3763 }
3764 }
3765
3766 Command::UpdateAnimIds(pack_key, starting_id, offset) => {
3767 match packs.get_mut(&pack_key) {
3768 Some(pack) => {
3769 match pack.update_anim_ids(game, starting_id, offset) {
3770 Ok(paths) => CentralCommand::send_back(&sender, Response::VecContainerPath(paths)),
3771 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3772 }
3773 }
3774 None => CentralCommand::send_back(&sender, Response::Error(format!("Pack not found: {}", pack_key))),
3775 }
3776 }
3777
3778 Command::GetTablesFromDependencies(table_name) => {
3779 let dependencies = dependencies.read().unwrap();
3780 match dependencies.db_data(&table_name, true, true) {
3781 Ok(files) => CentralCommand::send_back(&sender, Response::VecRFile(files.iter().map(|x| (**x).clone()).collect::<Vec<_>>())),
3782 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3783 }
3784 }
3785
3786 Command::ExportRigidToGltf(rigid, path) => {
3787 let mut dependencies = dependencies.write().unwrap();
3788 match gltf_from_rigid(&rigid, &mut dependencies) {
3789 Ok(gltf) => match save_gltf_to_disk(&gltf, &PathBuf::from(path)) {
3790 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3791 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3792 },
3793 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
3794 }
3795 }
3796
3797 Command::SettingsGetBool(key) => {
3799 CentralCommand::send_back(&sender, Response::Bool(settings.bool(&key)));
3800 }
3801 Command::SettingsGetI32(key) => {
3802 CentralCommand::send_back(&sender, Response::I32(settings.i32(&key)));
3803 }
3804 Command::SettingsGetF32(key) => {
3805 CentralCommand::send_back(&sender, Response::F32(settings.f32(&key)));
3806 }
3807 Command::SettingsGetString(key) => {
3808 CentralCommand::send_back(&sender, Response::String(settings.string(&key)));
3809 }
3810 Command::SettingsGetPathBuf(key) => {
3811 CentralCommand::send_back(&sender, Response::PathBuf(settings.path_buf(&key)));
3812 }
3813 Command::SettingsGetVecString(key) => {
3814 CentralCommand::send_back(&sender, Response::VecString(settings.vec_string(&key)));
3815 }
3816 Command::SettingsGetVecRaw(key) => {
3817 CentralCommand::send_back(&sender, Response::VecU8(settings.raw_data(&key)));
3818 }
3819 Command::SettingsGetAll => {
3820 CentralCommand::send_back(&sender, Response::SettingsAll(SettingsSnapshot {
3821 bool: settings.bool.clone(),
3822 i32: settings.i32.clone(),
3823 f32: settings.f32.clone(),
3824 string: settings.string.clone(),
3825 raw_data: settings.raw_data.clone(),
3826 vec_string: settings.vec_string.clone(),
3827 }));
3828 }
3829 Command::SettingsSetBool(key, value) => {
3830 match settings.set_bool(&key, value) {
3831 Ok(_) => {
3832 match key.as_str() {
3833 ENABLE_USAGE_TELEMETRY => rpfm_telemetry::set_usage_telemetry_enabled(value),
3834 ENABLE_CRASH_REPORTS => rpfm_telemetry::set_crash_reports_enabled(value),
3835 _ => {}
3836 }
3837 CentralCommand::send_back(&sender, Response::Success);
3838 }
3839 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3840 }
3841 }
3842 Command::SettingsSetI32(key, value) => {
3843 match settings.set_i32(&key, value) {
3844 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3845 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3846 }
3847 }
3848 Command::SettingsSetF32(key, value) => {
3849 match settings.set_f32(&key, value) {
3850 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3851 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3852 }
3853 }
3854 Command::SettingsSetString(key, value) => {
3855 match settings.set_string(&key, &value) {
3856 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3857 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3858 }
3859 }
3860 Command::SettingsSetPathBuf(key, value) => {
3861 match settings.set_path_buf(&key, &value) {
3862 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3863 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3864 }
3865 }
3866 Command::SettingsSetVecString(key, value) => {
3867 match settings.set_vec_string(&key, &value) {
3868 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3869 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3870 }
3871 }
3872 Command::SettingsSetVecRaw(key, value) => {
3873 match settings.set_raw_data(&key, &value) {
3874 Ok(_) => CentralCommand::send_back(&sender, Response::Success),
3875 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3876 }
3877 },
3878 Command::ConfigPath => {
3879 match config_path() {
3880 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3881 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3882 }
3883 },
3884 Command::AssemblyKitPath => {
3885 match settings.assembly_kit_path(game) {
3886 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3887 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3888 }
3889 },
3890 Command::BackupAutosavePath => {
3891 match backup_autosave_path() {
3892 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3893 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3894 }
3895 },
3896 Command::OldAkDataPath => {
3897 match old_ak_files_path() {
3898 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3899 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3900 }
3901 },
3902 Command::SchemasPath => {
3903 match schemas_path() {
3904 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3905 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3906 }
3907 },
3908 Command::TableProfilesPath => {
3909 match table_profiles_path() {
3910 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3911 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3912 }
3913 },
3914 Command::TranslationsLocalPath => {
3915 match translations_local_path() {
3916 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3917 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3918 }
3919 },
3920 Command::DependenciesCachePath => {
3921 match dependencies_cache_path() {
3922 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path)),
3923 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3924 }
3925 },
3926 Command::SettingsClearPath(path) => {
3927 match clear_config_path(&path) {
3928 Ok(()) => CentralCommand::send_back(&sender, Response::Success),
3929 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3930 }
3931 },
3932 Command::CustomConfigPath => {
3933 match custom_config_path() {
3934 Ok(path) => CentralCommand::send_back(&sender, Response::PathBuf(path.unwrap_or_default())),
3935 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3936 }
3937 },
3938 Command::SetCustomConfigPath(path) => {
3939 let path = if path.as_os_str().is_empty() { None } else { Some(path.as_path()) };
3940 match set_custom_config_path(path) {
3941 Ok(()) => CentralCommand::send_back(&sender, Response::Success),
3942 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3943 }
3944 },
3945 Command::BackupSettings => {
3946 backup_settings = settings.clone();
3947 CentralCommand::send_back(&sender, Response::Success);
3948 }
3949 Command::ClearSettings => match Settings::init(true) {
3950 Ok(set) => {
3951 settings = set;
3952 CentralCommand::send_back(&sender, Response::Success);},
3953 Err(e) => CentralCommand::send_back(&sender, Response::Error(e.to_string())),
3954 },
3955 Command::RestoreBackupSettings => {
3956 settings = backup_settings.clone();
3957 CentralCommand::send_back(&sender, Response::Success);
3958 }
3959 Command::OptimizerOptions => CentralCommand::send_back(&sender, Response::OptimizerOptions(settings.optimizer_options())),
3960
3961 Command::IsSchemaLoaded => CentralCommand::send_back(&sender, Response::Bool(schema.is_some())),
3962 Command::DefinitionsByTableName(name) => match schema {
3963 Some(ref schema) => {
3964 match schema.definitions_by_table_name(&name) {
3965 Some(defs) => CentralCommand::send_back(&sender, Response::VecDefinition(defs.to_vec())),
3966 None => CentralCommand::send_back(&sender, Response::VecDefinition(vec![])),
3967 }
3968 },
3969 None => CentralCommand::send_back(&sender, Response::Error(anyhow!("There is no Schema for the Game Selected.").to_string())),
3970 },
3971 Command::ReferencingColumnsForDefinition(name, definition) => match schema {
3972 Some(ref schema) => CentralCommand::send_back(&sender, Response::HashMapStringHashMapStringVecString(schema.referencing_columns_for_table(&name, &definition))),
3973 None => CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string())),
3974 },
3975 Command::Schema => match &schema {
3976 Some(schema) => CentralCommand::send_back(&sender, Response::Schema(schema.clone())),
3977 None => CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string())),
3978 }
3979 Command::DefinitionByTableNameAndVersion(name, version) => match schema {
3980 Some(ref schema) => match schema.definition_by_name_and_version(&name, version) {
3981 Some(def) => CentralCommand::send_back(&sender, Response::Definition(def.clone())),
3982 None => CentralCommand::send_back(&sender, Response::Error(format!("No definition found for table '{}' with version {}.", name, version))),
3983 },
3984 None => CentralCommand::send_back(&sender, Response::Error("There is no Schema for the Game Selected.".to_string())),
3985 },
3986
3987 Command::DeleteDefinition(name, version) => {
3988 if let Some(ref mut schema) = schema {
3989 schema.remove_definition(&name, version);
3990 }
3991 CentralCommand::send_back(&sender, Response::Success);
3992 }
3993
3994 Command::FieldsProcessed(definition) => {
3995 CentralCommand::send_back(&sender, Response::VecField(definition.fields_processed()));
3996 }
3997 }
3998 }
3999}
4000
4001fn load_schema(schema: &mut Option<Schema>, packs: &mut BTreeMap<String, Pack>, game: &GameInfo, settings: &Settings) {
4003
4004 for pack in packs.values_mut() {
4006 let cf = pack.compression_format();
4007 let mut files = pack.files_by_type_mut(&[FileType::DB]);
4008 let extra_data = Some(EncodeableExtraData::new_from_game_info_and_settings(game, cf, settings.bool("disable_uuid_regeneration_on_db_tables")));
4009
4010 files.par_iter_mut().for_each(|file| {
4011 let _ = file.encode(&extra_data, true, true, false);
4012 });
4013 }
4014
4015 let schema_path = schemas_path().unwrap().join(game.schema_file_name());
4017 let local_patches_path = table_patches_path().unwrap().join(game.schema_file_name());
4018 *schema = Schema::load(&schema_path, Some(&local_patches_path)).ok();
4019
4020 if let Some(ref schema) = schema {
4022 for pack in packs.values_mut() {
4023 let mut files = pack.files_by_type_mut(&[FileType::DB]);
4024 let mut extra_data = DecodeableExtraData::default();
4025 extra_data.set_schema(Some(schema));
4026 let extra_data = Some(extra_data);
4027
4028 files.par_iter_mut().for_each(|file| {
4029 let _ = file.decode(&extra_data, true, false);
4030 });
4031 }
4032 }
4033}
4034
4035fn decode_and_send_file(file: &mut RFile, sender: &UnboundedSender<Response>, settings: &Settings, game: &GameInfo, schema: &Option<Schema>) {
4036 let mut extra_data = DecodeableExtraData::default();
4037 extra_data.set_schema(schema.as_ref());
4038 extra_data.set_game_info(Some(game));
4039
4040 let mut ignored_file_types = vec![
4042 FileType::Anim,
4043 FileType::BMD,
4044 FileType::BMDVegetation,
4045 FileType::Dat,
4046 FileType::Font,
4047 FileType::HlslCompiled,
4048 FileType::Pack,
4049 FileType::SoundBank,
4050 FileType::Unknown
4051 ];
4052
4053 if !settings.bool("enable_esf_editor") {
4055 ignored_file_types.push(FileType::ESF);
4056 }
4057
4058 if ignored_file_types.contains(&file.file_type()) {
4059 return CentralCommand::send_back(sender, Response::Unknown);
4060 }
4061 let result = file.decode(&Some(extra_data), true, true).transpose().unwrap();
4062
4063 match result {
4064 Ok(RFileDecoded::AnimFragmentBattle(data)) => CentralCommand::send_back(sender, Response::AnimFragmentBattleRFileInfo(data, From::from(&*file))),
4065 Ok(RFileDecoded::AnimPack(data)) => CentralCommand::send_back(sender, Response::AnimPackRFileInfo(data.files().values().map(From::from).collect(), From::from(&*file))),
4066 Ok(RFileDecoded::AnimsTable(data)) => CentralCommand::send_back(sender, Response::AnimsTableRFileInfo(data, From::from(&*file))),
4067 Ok(RFileDecoded::Anim(_)) => CentralCommand::send_back(sender, Response::Unknown),
4068 Ok(RFileDecoded::Atlas(data)) => CentralCommand::send_back(sender, Response::AtlasRFileInfo(data, From::from(&*file))),
4069 Ok(RFileDecoded::Audio(data)) => CentralCommand::send_back(sender, Response::AudioRFileInfo(data, From::from(&*file))),
4070 Ok(RFileDecoded::BMD(_)) => CentralCommand::send_back(sender, Response::Unknown),
4071 Ok(RFileDecoded::BMDVegetation(_)) => CentralCommand::send_back(sender, Response::Unknown),
4072 Ok(RFileDecoded::Dat(_)) => CentralCommand::send_back(sender, Response::Unknown),
4073 Ok(RFileDecoded::DB(table)) => CentralCommand::send_back(sender, Response::DBRFileInfo(table, From::from(&*file))),
4074 Ok(RFileDecoded::ESF(data)) => CentralCommand::send_back(sender, Response::ESFRFileInfo(data, From::from(&*file))),
4075 Ok(RFileDecoded::Font(_)) => CentralCommand::send_back(sender, Response::Unknown),
4076 Ok(RFileDecoded::HlslCompiled(_)) => CentralCommand::send_back(sender, Response::Unknown),
4077 Ok(RFileDecoded::GroupFormations(data)) => CentralCommand::send_back(sender, Response::GroupFormationsRFileInfo(data, From::from(&*file))),
4078 Ok(RFileDecoded::Image(image)) => CentralCommand::send_back(sender, Response::ImageRFileInfo(image, From::from(&*file))),
4079 Ok(RFileDecoded::Loc(table)) => CentralCommand::send_back(sender, Response::LocRFileInfo(table, From::from(&*file))),
4080 Ok(RFileDecoded::MatchedCombat(data)) => CentralCommand::send_back(sender, Response::MatchedCombatRFileInfo(data, From::from(&*file))),
4081 Ok(RFileDecoded::Pack(_)) => CentralCommand::send_back(sender, Response::Unknown),
4082 Ok(RFileDecoded::PortraitSettings(data)) => CentralCommand::send_back(sender, Response::PortraitSettingsRFileInfo(data, From::from(&*file))),
4083 Ok(RFileDecoded::RigidModel(data)) => CentralCommand::send_back(sender, Response::RigidModelRFileInfo(data, From::from(&*file))),
4084 Ok(RFileDecoded::SoundBank(_)) => CentralCommand::send_back(sender, Response::Unknown),
4085 Ok(RFileDecoded::Text(text)) => CentralCommand::send_back(sender, Response::TextRFileInfo(text, From::from(&*file))),
4086 Ok(RFileDecoded::UIC(uic)) => CentralCommand::send_back(sender, Response::UICRFileInfo(uic, From::from(&*file))),
4087 Ok(RFileDecoded::UnitVariant(data)) => CentralCommand::send_back(sender, Response::UnitVariantRFileInfo(data, From::from(&*file))),
4088 Ok(RFileDecoded::Unknown(_)) => CentralCommand::send_back(sender, Response::Unknown),
4089 Ok(RFileDecoded::Video(data)) => CentralCommand::send_back(sender, Response::VideoInfoRFileInfo(From::from(&data), From::from(&*file))),
4090 Ok(RFileDecoded::VMD(data)) => CentralCommand::send_back(sender, Response::VMDRFileInfo(data, From::from(&*file))),
4091 Ok(RFileDecoded::WSModel(data)) => CentralCommand::send_back(sender, Response::WSModelRFileInfo(data, From::from(&*file))),
4092 Err(error) => CentralCommand::send_back(sender, Response::Error(error.to_string())),
4093 }
4094}
4095
4096fn exe_path() -> PathBuf {
4099 if cfg!(debug_assertions) {
4100 std::env::current_dir().unwrap()
4101 } else {
4102 let mut path = std::env::current_exe().unwrap();
4103 path.pop();
4104 path
4105 }
4106}
4107
4108fn git_update_check(
4111 sender: UnboundedSender<Response>,
4112 path_fn: fn() -> Result<PathBuf>,
4113 repo: &'static str,
4114 branch: &'static str,
4115 remote: &'static str,
4116) {
4117 tokio::spawn(async move {
4118 let result = tokio::task::spawn_blocking(move || {
4119 match path_fn() {
4120 Ok(local_path) => {
4121 let git_integration = GitIntegration::new(&local_path, repo, branch, remote);
4122 git_integration.check_update().map_err(|e| e.into())
4123 }
4124 Err(error) => Err(error),
4125 }
4126 }).await.unwrap();
4127
4128 match result {
4129 Ok(response) => CentralCommand::send_back(&sender, Response::APIResponseGit(response)),
4130 Err(error) => CentralCommand::send_back(&sender, Response::Error(error.to_string())),
4131 }
4132 });
4133}
4134
4135fn plugin_script_interpreter(path: &std::path::Path) -> Option<&'static str> {
4139 match path.extension().and_then(|extension| extension.to_str()) {
4140 Some("py") => Some("python"),
4141 Some("lua") => Some("lua"),
4142 _ => None,
4143 }
4144}
4145
4146fn container_path_from_disk_path(disk_path: &std::path::Path, base_folder: &std::path::Path) -> String {
4151 disk_path.strip_prefix(base_folder)
4152 .unwrap_or(disk_path)
4153 .components()
4154 .filter_map(|component| match component {
4155 std::path::Component::Normal(part) => Some(part.to_string_lossy().to_string()),
4156 _ => None,
4157 })
4158 .collect::<Vec<_>>()
4159 .join("/")
4160}
4161
4162fn delta_merge_files(sources: &[&RFile], merged_path: &str, dependencies: &Dependencies, resolutions: &[MergeResolution]) -> Result<DeltaMergeOutcome> {
4165 if sources.len() < 2 {
4166 return Err(anyhow!("Not enough tables provided to merge."));
4167 }
4168
4169 match sources[0].decoded()? {
4170 RFileDecoded::DB(_) => {
4171 let tables = sources.iter()
4172 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
4173 .collect::<Vec<_>>();
4174
4175 let baseline = db_baseline(dependencies, tables[0].1.table_name());
4176 let (merged, conflicts) = delta_merge_db(&tables, baseline.as_ref(), resolutions)?;
4177 if conflicts.is_empty() {
4178 Ok(DeltaMergeOutcome::Merged(RFile::new_from_decoded(&RFileDecoded::DB(merged), current_time()?, merged_path)))
4179 } else {
4180 Ok(DeltaMergeOutcome::Conflicts(conflicts))
4181 }
4182 },
4183 RFileDecoded::Loc(_) => {
4184 let tables = sources.iter()
4185 .filter_map(|file| if let Ok(RFileDecoded::Loc(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
4186 .collect::<Vec<_>>();
4187
4188 let baseline = loc_baseline(dependencies);
4189 let (merged, conflicts) = delta_merge_loc(&tables, baseline.as_ref(), resolutions)?;
4190 if conflicts.is_empty() {
4191 Ok(DeltaMergeOutcome::Merged(RFile::new_from_decoded(&RFileDecoded::Loc(merged), current_time()?, merged_path)))
4192 } else {
4193 Ok(DeltaMergeOutcome::Conflicts(conflicts))
4194 }
4195 },
4196 _ => Err(anyhow!("Delta merge is only supported for DB and Loc tables.")),
4197 }
4198}
4199
4200fn tr(s: &str) -> String {
4202 s.to_owned()
4203}