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