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