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