Skip to main content

rpfm_server/
background_thread.rs

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