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