1use std::collections::HashSet;
20use std::fs::create_dir_all;
21use std::path::Path;
22
23use unicase::{eq, UniCase};
24
25use super::mutable::MutableLoadOrder;
26use super::readable::{ReadableLoadOrder, ReadableLoadOrderBase};
27use crate::enums::Error;
28use crate::plugin::Plugin;
29use crate::GameSettings;
30
31const MAX_ACTIVE_LIGHT_PLUGINS: usize = 4096;
32const MAX_ACTIVE_MEDIUM_PLUGINS: usize = 256;
33
34pub trait WritableLoadOrder: ReadableLoadOrder + std::fmt::Debug {
35 fn game_settings_mut(&mut self) -> &mut GameSettings;
36
37 fn load(&mut self) -> Result<(), Error>;
38
39 fn save(&mut self) -> Result<(), Error>;
40
41 fn add(&mut self, plugin_name: &str) -> Result<usize, Error>;
42
43 fn remove(&mut self, plugin_name: &str) -> Result<(), Error>;
44
45 fn set_load_order(&mut self, plugin_names: &[&str]) -> Result<(), Error>;
46
47 fn set_plugin_index(&mut self, plugin_name: &str, position: usize) -> Result<usize, Error>;
48
49 fn is_self_consistent(&self) -> Result<bool, Error>;
50
51 fn is_ambiguous(&self) -> Result<bool, Error>;
52
53 fn activate(&mut self, plugin_name: &str) -> Result<(), Error>;
54
55 fn deactivate(&mut self, plugin_name: &str) -> Result<(), Error>;
56
57 fn set_active_plugins(&mut self, active_plugin_names: &[&str]) -> Result<(), Error>;
58}
59
60pub(super) fn add<T: MutableLoadOrder>(
61 load_order: &mut T,
62 plugin_name: &str,
63) -> Result<usize, Error> {
64 if load_order.index_of(plugin_name).is_some() {
65 Err(Error::DuplicatePlugin(plugin_name.to_owned()))
66 } else {
67 let plugin = Plugin::new(plugin_name, load_order.game_settings())?;
68
69 if let Some(position) = load_order.insert_position(&plugin) {
70 load_order.validate_index(&plugin, position)?;
71 load_order.plugins_mut().insert(position, plugin);
72 Ok(position)
73 } else {
74 load_order.validate_index(&plugin, load_order.plugins().len())?;
75 load_order.plugins_mut().push(plugin);
76 Ok(load_order.plugins().len() - 1)
77 }
78 }
79}
80
81pub(super) fn remove<T: MutableLoadOrder>(
82 load_order: &mut T,
83 plugin_name: &str,
84) -> Result<(), Error> {
85 match load_order.find_plugin_and_index(plugin_name) {
86 Some((index, plugin)) => {
87 let plugin_path = load_order.game_settings().plugin_path(plugin_name);
88 if plugin_path.exists() {
89 return Err(Error::InstalledPlugin(plugin_name.to_owned()));
90 }
91
92 if plugin.is_master_file() {
97 let next_master = &load_order
98 .plugins()
99 .iter()
100 .skip(index + 1)
101 .find(|p| p.is_master_file());
102
103 if let Some(next_master) = next_master {
104 let next_master_masters = next_master.masters()?;
105 let next_master_master_names: HashSet<_> =
106 next_master_masters.iter().map(UniCase::new).collect();
107
108 let mut masters = plugin.masters()?;
109
110 masters.retain(|m| !next_master_master_names.contains(&UniCase::new(m)));
112
113 if let Some(n) = masters.iter().find(|n| {
115 load_order
116 .find_plugin(n)
117 .is_some_and(|p| !p.is_master_file())
120 }) {
121 return Err(Error::NonMasterBeforeMaster {
122 master: plugin_name.to_owned(),
123 non_master: n.to_owned(),
124 });
125 }
126 }
127 }
128
129 load_order.plugins_mut().remove(index);
130
131 Ok(())
132 }
133 None => Err(Error::PluginNotFound(plugin_name.to_owned())),
134 }
135}
136
137#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
138struct PluginCounts {
139 light: usize,
140 medium: usize,
141 full: usize,
142}
143
144impl PluginCounts {
145 fn count_plugin(&mut self, plugin: &Plugin) {
146 if plugin.is_light_plugin() {
147 self.light += 1;
148 } else if plugin.is_medium_plugin() {
149 self.medium += 1;
150 } else {
151 self.full += 1;
152 }
153 }
154}
155
156fn count_active_plugins<T: ReadableLoadOrderBase>(load_order: &T) -> PluginCounts {
157 let mut counts = PluginCounts::default();
158
159 for plugin in load_order.plugins().iter().filter(|p| p.is_active()) {
160 counts.count_plugin(plugin);
161 }
162
163 counts
164}
165
166fn validate_plugin_counts(
167 counts: &PluginCounts,
168 max_active_full_plugins: usize,
169) -> Result<(), Error> {
170 if (counts.light > MAX_ACTIVE_LIGHT_PLUGINS)
171 || (counts.medium > MAX_ACTIVE_MEDIUM_PLUGINS)
172 || (counts.full > max_active_full_plugins)
173 {
174 Err(Error::TooManyActivePlugins {
175 light_count: counts.light,
176 medium_count: counts.medium,
177 full_count: counts.full,
178 })
179 } else {
180 Ok(())
181 }
182}
183
184fn count_plugins(existing_plugins: &[Plugin], existing_plugin_indexes: &[usize]) -> PluginCounts {
185 let mut counts = PluginCounts::default();
186
187 for index in existing_plugin_indexes {
188 if let Some(plugin) = existing_plugins.get(*index) {
189 counts.count_plugin(plugin);
190 }
191 }
192
193 counts
194}
195
196pub(super) fn activate<T: MutableLoadOrder>(
197 load_order: &mut T,
198 plugin_name: &str,
199) -> Result<(), Error> {
200 if load_order
201 .game_settings()
202 .supports_blueprint_ships_plugins()
203 {
204 return activate_with_blueprint_ships_plugin(load_order, plugin_name);
205 }
206
207 let mut counts = count_active_plugins(load_order);
208 let max_active_full_plugins = load_order.max_active_full_plugins();
209
210 let Some(plugin) = load_order.find_plugin_mut(plugin_name) else {
211 return Err(Error::PluginNotFound(plugin_name.to_owned()));
212 };
213
214 if !plugin.is_active() {
215 counts.count_plugin(plugin);
216
217 validate_plugin_counts(&counts, max_active_full_plugins)?;
218 }
219
220 plugin.activate()
221}
222
223fn activate_with_blueprint_ships_plugin<T: MutableLoadOrder>(
224 load_order: &mut T,
225 plugin_name: &str,
226) -> Result<(), Error> {
227 let Some((plugin_index, plugin)) = load_order.find_plugin_and_index(plugin_name) else {
228 return Err(Error::PluginNotFound(plugin_name.to_owned()));
229 };
230
231 let blueprint_ships_pair = find_blueprint_ships_plugin_for_plugin(load_order, plugin_name)
234 .filter(|(_, p)| !p.is_active());
235
236 if !plugin.is_active() {
237 let max_active_full_plugins = load_order.max_active_full_plugins();
238 let mut counts = count_active_plugins(load_order);
239
240 counts.count_plugin(plugin);
241
242 if let Some((_, blueprint_ships_plugin)) = blueprint_ships_pair {
243 counts.count_plugin(blueprint_ships_plugin);
244 }
245
246 validate_plugin_counts(&counts, max_active_full_plugins)?;
247 }
248
249 let blueprint_ships_plugin_index = blueprint_ships_pair.map(|(i, _)| i);
251
252 if let Some(plugin) = load_order.plugins_mut().get_mut(plugin_index) {
253 plugin.activate()?;
254 }
255
256 if let Some(index) = blueprint_ships_plugin_index {
257 if let Some(plugin) = load_order.plugins_mut().get_mut(index) {
258 plugin.implicitly_activate()?;
259 }
260 }
261
262 Ok(())
263}
264
265fn find_blueprint_ships_plugin_for_plugin<'a, T: ReadableLoadOrderBase>(
266 load_order: &'a T,
267 plugin_name: &str,
268) -> Option<(usize, &'a Plugin)> {
269 if load_order
270 .game_settings()
271 .supports_blueprint_ships_plugins()
272 {
273 blueprint_ships_plugin_name(plugin_name).and_then(|n| load_order.find_plugin_and_index(&n))
274 } else {
275 None
276 }
277}
278
279pub(super) fn deactivate<T: MutableLoadOrder>(
280 load_order: &mut T,
281 plugin_name: &str,
282) -> Result<(), Error> {
283 if is_implicitly_active(load_order, plugin_name) {
284 return Err(Error::ImplicitlyActivePlugin(plugin_name.to_owned()));
285 }
286
287 load_order
288 .find_plugin_mut(plugin_name)
289 .ok_or_else(|| Error::PluginNotFound(plugin_name.to_owned()))
290 .map(Plugin::deactivate)?;
291
292 if load_order
293 .game_settings()
294 .supports_blueprint_ships_plugins()
295 {
296 if let Some(plugin) = blueprint_ships_plugin_name(plugin_name)
299 .filter(|n| !is_implicitly_active(load_order, n))
300 .and_then(|n| load_order.find_plugin_mut(&n))
301 .filter(|p| !p.is_explicitly_active())
302 {
303 plugin.deactivate();
304 }
305 }
306
307 Ok(())
308}
309
310fn is_implicitly_active<T: ReadableLoadOrder + ReadableLoadOrderBase>(
311 load_order: &T,
312 plugin_name: &str,
313) -> bool {
314 load_order.game_settings().is_implicitly_active(plugin_name)
315 || is_implicitly_activated_by_another_plugin(load_order, plugin_name)
316}
317
318fn is_implicitly_activated_by_another_plugin<T: ReadableLoadOrder + ReadableLoadOrderBase>(
319 load_order: &T,
320 plugin_name: &str,
321) -> bool {
322 if !load_order
323 .game_settings()
324 .supports_blueprint_ships_plugins()
325 {
326 return false;
327 }
328
329 let Some(name_without_extension) = blueprint_ships_base_plugin_name(plugin_name) else {
330 return false;
331 };
332
333 load_order
334 .plugins()
335 .iter()
336 .filter(|p| p.is_active())
337 .map(Plugin::name_without_extension)
338 .any(|n| unicase::eq(n, name_without_extension))
339}
340
341pub(super) fn set_active_plugins<T: MutableLoadOrder>(
342 load_order: &mut T,
343 active_plugin_names: &[&str],
344) -> Result<(), Error> {
345 let existing_plugin_indices = load_order.lookup_plugins(active_plugin_names)?;
346
347 let counts = count_plugins(load_order.plugins(), &existing_plugin_indices);
348
349 validate_plugin_counts(&counts, load_order.max_active_full_plugins())?;
350
351 for plugin_name in load_order.game_settings().implicitly_active_plugins() {
352 validate_plugin_is_active(load_order, active_plugin_names, plugin_name)?;
355 }
356
357 if load_order
358 .game_settings()
359 .supports_blueprint_ships_plugins()
360 {
361 for active_plugin in active_plugin_names {
365 if let Some(blueprint_ships_plugin_name) = blueprint_ships_plugin_name(active_plugin) {
366 validate_plugin_is_active(
367 load_order,
368 active_plugin_names,
369 &blueprint_ships_plugin_name,
370 )?;
371 }
372 }
373 }
374
375 load_order.deactivate_all();
376
377 for index in existing_plugin_indices {
378 if let Some(plugin) = load_order.plugins_mut().get_mut(index) {
379 plugin.activate()?;
382 }
383 }
384
385 Ok(())
386}
387
388fn blueprint_ships_plugin_name(plugin_name: &str) -> Option<String> {
389 const EXTENSION_LENGTH: usize = 4;
391
392 plugin_name
393 .get(..plugin_name.len() - EXTENSION_LENGTH)
394 .map(|n| format!("BlueprintShips-{n}.esm"))
395}
396
397pub(super) fn blueprint_ships_base_plugin_name(blueprint_ships_plugin_name: &str) -> Option<&str> {
398 const BLUEPRINT_SHIPS_PREFIX: &str = "BlueprintShips-";
399 const BLUEPRINT_SHIPS_SUFFIX: &str = ".esm";
400
401 blueprint_ships_plugin_name
402 .split_at_checked(BLUEPRINT_SHIPS_PREFIX.len())
403 .filter(|(prefix, _)| BLUEPRINT_SHIPS_PREFIX.eq_ignore_ascii_case(prefix))
404 .and_then(|(_, remainder)| {
405 remainder
406 .split_at_checked(remainder.len() - BLUEPRINT_SHIPS_SUFFIX.len())
407 .filter(|(_, suffix)| BLUEPRINT_SHIPS_SUFFIX.eq_ignore_ascii_case(suffix))
408 .map(|(base, _)| base)
409 })
410}
411
412fn validate_plugin_is_active<T: MutableLoadOrder>(
413 load_order: &T,
414 active_plugin_names: &[&str],
415 plugin_name: &str,
416) -> Result<(), Error> {
417 if load_order.index_of(plugin_name).is_some()
418 && !active_plugin_names.iter().any(|p| eq(*p, plugin_name))
419 {
420 return Err(Error::ImplicitlyActivePlugin(plugin_name.to_owned()));
421 }
422
423 Ok(())
424}
425
426pub(super) fn create_parent_dirs(path: &Path) -> Result<(), Error> {
427 if let Some(x) = path.parent() {
428 if !x.exists() {
429 create_dir_all(x).map_err(|e| Error::IoError(x.to_path_buf(), e))?;
430 }
431 }
432 Ok(())
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 use std::fs::remove_file;
440
441 use tempfile::tempdir;
442
443 use crate::enums::GameId;
444 use crate::load_order::tests::{
445 game_settings_for_test, load_and_insert, mock_game_files, prepare_bulk_full_plugins,
446 prepare_bulk_plugins, prepend_early_loader, prepend_master, set_blueprint_flag,
447 set_master_flag,
448 };
449 use crate::plugin::ActiveState;
450 use crate::tests::{copy_to_test_dir, NON_ASCII};
451
452 struct TestLoadOrder {
453 game_settings: GameSettings,
454 plugins: Vec<Plugin>,
455 }
456
457 impl ReadableLoadOrderBase for TestLoadOrder {
458 fn game_settings_base(&self) -> &GameSettings {
459 &self.game_settings
460 }
461
462 fn plugins(&self) -> &[Plugin] {
463 &self.plugins
464 }
465 }
466
467 impl MutableLoadOrder for TestLoadOrder {
468 fn plugins_mut(&mut self) -> &mut Vec<Plugin> {
469 &mut self.plugins
470 }
471 }
472
473 fn prepare(game_id: GameId, game_dir: &Path) -> TestLoadOrder {
474 let mut game_settings = game_settings_for_test(game_id, game_dir);
475 mock_game_files(&mut game_settings);
476
477 let mut plugins =
478 vec![
479 Plugin::with_active("Blank.esp", &game_settings, ActiveState::ExplicitlyActive)
480 .unwrap(),
481 ];
482
483 if game_id != GameId::Starfield {
484 plugins.push(Plugin::new("Blank - Different.esp", &game_settings).unwrap());
485 }
486
487 TestLoadOrder {
488 game_settings,
489 plugins,
490 }
491 }
492
493 fn prepare_bulk_medium_plugins(load_order: &mut TestLoadOrder) -> Vec<String> {
494 prepare_bulk_plugins(load_order, "Blank.medium.esm", 260, |i| {
495 format!("Blank{i}.medium.esm")
496 })
497 }
498
499 fn prepare_bulk_light_plugins(load_order: &mut TestLoadOrder) -> Vec<String> {
500 prepare_bulk_plugins(load_order, "Blank.small.esm", 5000, |i| {
501 format!("Blank{i}.small.esm")
502 })
503 }
504
505 #[test]
506 fn add_should_error_if_the_plugin_is_already_in_the_load_order() {
507 let tmp_dir = tempdir().unwrap();
508 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
509
510 assert!(add(&mut load_order, "Blank.esm").is_ok());
511 assert!(add(&mut load_order, "Blank.esm").is_err());
512 }
513
514 #[test]
515 fn add_should_error_if_given_a_master_that_would_hoist_a_non_master() {
516 let tmp_dir = tempdir().unwrap();
517 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
518
519 let plugins_dir = &load_order.game_settings().plugins_directory();
520 copy_to_test_dir(
521 "Blank - Different.esm",
522 "Blank - Different.esm",
523 load_order.game_settings(),
524 );
525 set_master_flag(
526 GameId::Oblivion,
527 &plugins_dir.join("Blank - Different.esm"),
528 false,
529 )
530 .unwrap();
531 assert!(add(&mut load_order, "Blank - Different.esm").is_ok());
532
533 copy_to_test_dir(
534 "Blank - Different Master Dependent.esm",
535 "Blank - Different Master Dependent.esm",
536 load_order.game_settings(),
537 );
538
539 assert!(add(&mut load_order, "Blank - Different Master Dependent.esm").is_err());
540 }
541
542 #[test]
543 fn add_should_error_if_the_plugin_is_not_valid() {
544 let tmp_dir = tempdir().unwrap();
545 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
546
547 assert!(add(&mut load_order, "invalid.esm").is_err());
548 }
549
550 #[test]
551 fn add_should_insert_a_master_before_non_masters() {
552 let tmp_dir = tempdir().unwrap();
553 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
554
555 assert!(!load_order.plugins[1].is_master_file());
556
557 assert_eq!(0, add(&mut load_order, "Blank.esm").unwrap());
558 assert_eq!(0, load_order.index_of("Blank.esm").unwrap());
559 }
560
561 #[test]
562 fn add_should_append_a_non_master() {
563 let tmp_dir = tempdir().unwrap();
564 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
565
566 assert_eq!(
567 2,
568 add(&mut load_order, "Blank - Master Dependent.esp").unwrap()
569 );
570 assert_eq!(
571 2,
572 load_order.index_of("Blank - Master Dependent.esp").unwrap()
573 );
574 }
575
576 #[test]
577 fn add_should_hoist_a_non_master_that_a_master_depends_on() {
578 let tmp_dir = tempdir().unwrap();
579 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
580
581 let plugins_dir = &load_order.game_settings().plugins_directory();
582 copy_to_test_dir(
583 "Blank - Different Master Dependent.esm",
584 "Blank - Different Master Dependent.esm",
585 load_order.game_settings(),
586 );
587 assert!(add(&mut load_order, "Blank - Different Master Dependent.esm").is_ok());
588
589 copy_to_test_dir(
590 "Blank - Different.esm",
591 "Blank - Different.esm",
592 load_order.game_settings(),
593 );
594 set_master_flag(
595 GameId::Oblivion,
596 &plugins_dir.join("Blank - Different.esm"),
597 false,
598 )
599 .unwrap();
600 assert_eq!(0, add(&mut load_order, "Blank - Different.esm").unwrap());
601 }
602
603 #[test]
604 fn add_should_hoist_a_master_that_a_master_depends_on() {
605 let tmp_dir = tempdir().unwrap();
606 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
607
608 let plugin_name = "Blank - Master Dependent.esm";
609 copy_to_test_dir(plugin_name, plugin_name, load_order.game_settings());
610 assert_eq!(0, add(&mut load_order, plugin_name).unwrap());
611
612 assert_eq!(0, add(&mut load_order, "Blank.esm").unwrap());
613 }
614
615 #[test]
616 fn remove_should_error_if_the_plugin_is_not_in_the_load_order() {
617 let tmp_dir = tempdir().unwrap();
618 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
619 assert!(remove(&mut load_order, "Blank.esm").is_err());
620 }
621
622 #[test]
623 fn remove_should_error_if_the_plugin_is_installed() {
624 let tmp_dir = tempdir().unwrap();
625 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
626 assert!(remove(&mut load_order, "Blank.esp").is_err());
627 }
628
629 #[test]
630 fn remove_should_error_if_removing_a_master_would_leave_a_non_master_it_hoisted_loading_too_early(
631 ) {
632 let tmp_dir = tempdir().unwrap();
633 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
634
635 prepend_master(&mut load_order);
636
637 let plugin_to_remove = "Blank - Different Master Dependent.esm";
638
639 let plugins_dir = &load_order.game_settings().plugins_directory();
640 copy_to_test_dir(
641 plugin_to_remove,
642 plugin_to_remove,
643 load_order.game_settings(),
644 );
645 assert!(add(&mut load_order, plugin_to_remove).is_ok());
646
647 copy_to_test_dir(
648 "Blank - Different.esm",
649 "Blank - Different.esm",
650 load_order.game_settings(),
651 );
652 set_master_flag(
653 GameId::Oblivion,
654 &plugins_dir.join("Blank - Different.esm"),
655 false,
656 )
657 .unwrap();
658 assert_eq!(1, add(&mut load_order, "Blank - Different.esm").unwrap());
659
660 copy_to_test_dir(
661 "Blank - Master Dependent.esm",
662 "Blank - Master Dependent.esm",
663 load_order.game_settings(),
664 );
665 assert!(add(&mut load_order, "Blank - Master Dependent.esm").is_ok());
666
667 let blank_master_dependent = load_order.plugins.remove(1);
668 load_order.plugins.insert(3, blank_master_dependent);
669
670 std::fs::remove_file(plugins_dir.join(plugin_to_remove)).unwrap();
671
672 match remove(&mut load_order, plugin_to_remove).unwrap_err() {
673 Error::NonMasterBeforeMaster { master, non_master } => {
674 assert_eq!("Blank - Different Master Dependent.esm", master);
675 assert_eq!("Blank - Different.esm", non_master);
676 }
677 e => panic!("Unexpected error type: {e:?}"),
678 }
679 }
680
681 #[test]
682 fn remove_should_allow_removal_of_a_master_that_depends_on_a_blueprint_plugin() {
683 let tmp_dir = tempdir().unwrap();
684 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
685
686 let plugins_dir = &load_order.game_settings().plugins_directory();
687
688 let plugin_to_remove = "Blank - Override.full.esm";
689 copy_to_test_dir(
690 plugin_to_remove,
691 plugin_to_remove,
692 load_order.game_settings(),
693 );
694 assert!(add(&mut load_order, plugin_to_remove).is_ok());
695
696 let blueprint_plugin = "Blank.full.esm";
697 copy_to_test_dir(
698 blueprint_plugin,
699 blueprint_plugin,
700 load_order.game_settings(),
701 );
702 set_blueprint_flag(GameId::Starfield, &plugins_dir.join(blueprint_plugin), true).unwrap();
703 assert_eq!(2, add(&mut load_order, blueprint_plugin).unwrap());
704
705 let following_master_plugin = "Blank.medium.esm";
706 copy_to_test_dir(
707 following_master_plugin,
708 following_master_plugin,
709 load_order.game_settings(),
710 );
711 assert!(add(&mut load_order, following_master_plugin).is_ok());
712
713 std::fs::remove_file(plugins_dir.join(plugin_to_remove)).unwrap();
714
715 assert!(remove(&mut load_order, plugin_to_remove).is_ok());
716 }
717
718 #[test]
719 fn remove_should_remove_the_given_plugin_from_the_load_order() {
720 let tmp_dir = tempdir().unwrap();
721 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
722
723 remove_file(
724 load_order
725 .game_settings()
726 .plugins_directory()
727 .join("Blank.esp"),
728 )
729 .unwrap();
730
731 assert!(remove(&mut load_order, "Blank.esp").is_ok());
732 assert!(load_order.index_of("Blank.esp").is_none());
733 }
734
735 #[test]
736 fn activate_should_activate_the_plugin_with_the_given_filename() {
737 let tmp_dir = tempdir().unwrap();
738 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
739
740 assert!(activate(&mut load_order, "Blank - Different.esp").is_ok());
741 assert!(load_order.is_active("Blank - Different.esp"));
742 }
743
744 #[test]
745 fn activate_should_error_if_the_plugin_is_not_valid() {
746 let tmp_dir = tempdir().unwrap();
747 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
748
749 assert!(activate(&mut load_order, "missing.esp").is_err());
750 assert!(load_order.index_of("missing.esp").is_none());
751 }
752
753 #[test]
754 fn activate_should_error_if_the_plugin_is_not_already_in_the_load_order() {
755 let tmp_dir = tempdir().unwrap();
756 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
757
758 assert!(activate(&mut load_order, "Blank.esm").is_err());
759 assert!(!load_order.is_active("Blank.esm"));
760 }
761
762 #[test]
763 fn activate_should_be_case_insensitive() {
764 let tmp_dir = tempdir().unwrap();
765 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
766
767 assert!(activate(&mut load_order, "Blank - different.esp").is_ok());
768 assert!(load_order.is_active("Blank - Different.esp"));
769 }
770
771 #[test]
772 fn activate_should_throw_if_increasing_the_number_of_active_plugins_past_the_limit() {
773 let tmp_dir = tempdir().unwrap();
774 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
775
776 let plugins = prepare_bulk_full_plugins(&mut load_order);
777 for plugin in &plugins[..254] {
778 activate(&mut load_order, plugin).unwrap();
779 }
780
781 assert!(activate(&mut load_order, "Blank - Different.esp").is_err());
782 assert!(!load_order.is_active("Blank - Different.esp"));
783 }
784
785 #[test]
786 fn activate_should_succeed_if_at_the_active_plugins_limit_and_the_plugin_is_already_active() {
787 let tmp_dir = tempdir().unwrap();
788 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
789
790 let plugins = prepare_bulk_full_plugins(&mut load_order);
791 for plugin in &plugins[..254] {
792 activate(&mut load_order, plugin).unwrap();
793 }
794
795 assert!(load_order.is_active("Blank.esp"));
796 assert!(activate(&mut load_order, "Blank.esp").is_ok());
797 }
798
799 #[test]
800 fn activate_should_fail_if_at_the_active_plugins_limit_and_the_plugin_is_an_update_plugin() {
801 let tmp_dir = tempdir().unwrap();
802 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
803
804 let plugins = prepare_bulk_full_plugins(&mut load_order);
805 for plugin in &plugins[..254] {
806 activate(&mut load_order, plugin).unwrap();
807 }
808
809 let plugin = "Blank - Override.esp";
810 load_and_insert(&mut load_order, plugin);
811
812 assert!(!load_order.is_active(plugin));
813
814 assert!(activate(&mut load_order, plugin).is_err());
815 assert!(!load_order.is_active(plugin));
816 }
817
818 #[test]
819 fn activate_should_count_active_update_plugins_towards_limit() {
820 let tmp_dir = tempdir().unwrap();
821 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
822
823 let plugins = prepare_bulk_full_plugins(&mut load_order);
824 for plugin in &plugins[..254] {
825 activate(&mut load_order, plugin).unwrap();
826 }
827
828 let plugin = "Blank - Override.esp";
829 load_and_insert(&mut load_order, plugin);
830
831 assert!(!load_order.is_active(plugin));
832
833 assert!(activate(&mut load_order, plugin).is_err());
834 assert!(!load_order.is_active(plugin));
835 }
836
837 #[test]
838 fn activate_should_lower_the_full_plugin_limit_if_a_light_plugin_is_present() {
839 let tmp_dir = tempdir().unwrap();
840 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
841
842 let plugins = prepare_bulk_full_plugins(&mut load_order);
843 for plugin in &plugins[..252] {
844 activate(&mut load_order, plugin).unwrap();
845 }
846
847 let plugin = "Blank.small.esm";
848 load_and_insert(&mut load_order, plugin);
849 activate(&mut load_order, plugin).unwrap();
850
851 let plugin = &plugins[253];
852 assert!(!load_order.is_active(plugin));
853
854 assert!(activate(&mut load_order, plugin).is_ok());
855 assert!(load_order.is_active(plugin));
856
857 let plugin = &plugins[254];
858 assert!(!load_order.is_active(plugin));
859
860 assert!(activate(&mut load_order, plugin).is_err());
861 assert!(!load_order.is_active(plugin));
862 }
863
864 #[test]
865 fn activate_should_lower_the_full_plugin_limit_if_a_medium_plugin_is_present() {
866 let tmp_dir = tempdir().unwrap();
867 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
868
869 let plugins = prepare_bulk_full_plugins(&mut load_order);
870 for plugin in &plugins[..252] {
871 activate(&mut load_order, plugin).unwrap();
872 }
873
874 let plugin = "Blank.medium.esm";
875 load_and_insert(&mut load_order, plugin);
876 activate(&mut load_order, plugin).unwrap();
877
878 let plugin = &plugins[253];
879 assert!(!load_order.is_active(plugin));
880
881 assert!(activate(&mut load_order, plugin).is_ok());
882 assert!(load_order.is_active(plugin));
883
884 let plugin = &plugins[254];
885 assert!(!load_order.is_active(plugin));
886
887 assert!(activate(&mut load_order, plugin).is_err());
888 assert!(!load_order.is_active(plugin));
889 }
890
891 #[test]
892 fn activate_should_lower_the_full_plugin_limit_if_light_and_medium_plugins_are_present() {
893 let tmp_dir = tempdir().unwrap();
894 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
895
896 let plugins = prepare_bulk_full_plugins(&mut load_order);
897 for plugin in &plugins[..251] {
898 activate(&mut load_order, plugin).unwrap();
899 }
900
901 for plugin in ["Blank.medium.esm", "Blank.small.esm"] {
902 load_and_insert(&mut load_order, plugin);
903 activate(&mut load_order, plugin).unwrap();
904 }
905
906 let plugin = &plugins[252];
907 assert!(!load_order.is_active(plugin));
908
909 assert!(activate(&mut load_order, plugin).is_ok());
910 assert!(load_order.is_active(plugin));
911
912 let plugin = &plugins[253];
913 assert!(!load_order.is_active(plugin));
914
915 assert!(activate(&mut load_order, plugin).is_err());
916 assert!(!load_order.is_active(plugin));
917 }
918
919 #[test]
920 fn activate_should_check_full_medium_and_small_plugins_active_limits_separately() {
921 let tmp_dir = tempdir().unwrap();
922 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
923
924 let full = prepare_bulk_full_plugins(&mut load_order);
925 let medium = prepare_bulk_medium_plugins(&mut load_order);
926 let light = prepare_bulk_light_plugins(&mut load_order);
927
928 let mut plugin_refs = Vec::with_capacity(4603);
929 plugin_refs.extend(full[..252].iter().map(String::as_str));
930 plugin_refs.extend(medium[..255].iter().map(String::as_str));
931 plugin_refs.extend(light[..4095].iter().map(String::as_str));
932
933 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
934
935 let plugin = &full[252];
936 assert!(!load_order.is_active(plugin));
937 assert!(activate(&mut load_order, plugin).is_ok());
938 assert!(load_order.is_active(plugin));
939
940 let plugin = &medium[255];
941 assert!(!load_order.is_active(plugin));
942 assert!(activate(&mut load_order, plugin).is_ok());
943 assert!(load_order.is_active(plugin));
944
945 let plugin = &light[4095];
946 assert!(!load_order.is_active(plugin));
947 assert!(activate(&mut load_order, plugin).is_ok());
948 assert!(load_order.is_active(plugin));
949
950 let plugin = &full[253];
951 assert!(activate(&mut load_order, plugin).is_err());
952 assert!(!load_order.is_active(plugin));
953
954 let plugin = &medium[256];
955 assert!(activate(&mut load_order, plugin).is_err());
956 assert!(!load_order.is_active(plugin));
957
958 let plugin = &light[4096];
959 assert!(activate(&mut load_order, plugin).is_err());
960 assert!(!load_order.is_active(plugin));
961 }
962
963 #[test]
964 fn activate_should_explicitly_activate_an_implicitly_active_plugin() {
965 let tmp_dir = tempdir().unwrap();
966 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
967
968 let plugin_name = "Blank - Different.esp";
969 load_order
970 .find_plugin_mut(plugin_name)
971 .unwrap()
972 .implicitly_activate()
973 .unwrap();
974
975 assert!(load_order.is_active(plugin_name));
976 assert!(!load_order
977 .find_plugin(plugin_name)
978 .unwrap()
979 .is_explicitly_active());
980
981 activate(&mut load_order, plugin_name).unwrap();
982
983 assert!(load_order.is_active(plugin_name));
984 assert!(load_order
985 .find_plugin(plugin_name)
986 .unwrap()
987 .is_explicitly_active());
988 }
989
990 #[test]
991 fn activate_with_blueprint_ship_base_plugin_should_also_activate_blueprint_ships_plugin() {
992 let tmp_dir = tempdir().unwrap();
993 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
994
995 let plugin_name = "Blank.esp";
996 deactivate(&mut load_order, plugin_name).unwrap();
997
998 let blueprint_ships = "BlueprintShips-Blank.esm";
999 copy_to_test_dir(
1000 "Blank.full.esm",
1001 blueprint_ships,
1002 load_order.game_settings(),
1003 );
1004 add(&mut load_order, blueprint_ships).unwrap();
1005
1006 assert!(!load_order.is_active(plugin_name));
1007 assert!(!load_order.is_active(blueprint_ships));
1008
1009 activate(&mut load_order, plugin_name).unwrap();
1010
1011 assert!(load_order.is_active(plugin_name));
1012 assert!(load_order.is_active(blueprint_ships));
1013 assert!(load_order
1014 .find_plugin(plugin_name)
1015 .unwrap()
1016 .is_explicitly_active());
1017 assert!(!load_order
1018 .find_plugin(blueprint_ships)
1019 .unwrap()
1020 .is_explicitly_active());
1021 }
1022
1023 #[test]
1024 fn activate_should_explicitly_activate_an_implicitly_active_plugin_and_implicitly_active_its_blueprintships_plugin(
1025 ) {
1026 let tmp_dir = tempdir().unwrap();
1027 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1028
1029 let plugin_name = "Blank.esp";
1030 deactivate(&mut load_order, plugin_name).unwrap();
1031 load_order
1032 .find_plugin_mut(plugin_name)
1033 .unwrap()
1034 .implicitly_activate()
1035 .unwrap();
1036
1037 let blueprint_ships = "BlueprintShips-Blank.esm";
1038 copy_to_test_dir(
1039 "Blank.full.esm",
1040 blueprint_ships,
1041 load_order.game_settings(),
1042 );
1043 add(&mut load_order, blueprint_ships).unwrap();
1044
1045 assert!(load_order.is_active(plugin_name));
1046 assert!(!load_order
1047 .find_plugin(plugin_name)
1048 .unwrap()
1049 .is_explicitly_active());
1050 assert!(!load_order.is_active(blueprint_ships));
1051
1052 activate(&mut load_order, plugin_name).unwrap();
1053
1054 assert!(load_order.is_active(plugin_name));
1055 assert!(load_order.is_active(blueprint_ships));
1056 assert!(load_order
1057 .find_plugin(plugin_name)
1058 .unwrap()
1059 .is_explicitly_active());
1060 assert!(!load_order
1061 .find_plugin(blueprint_ships)
1062 .unwrap()
1063 .is_explicitly_active());
1064 }
1065
1066 #[test]
1067 fn activate_should_succeed_if_blueprint_ships_plugins_are_supported_but_not_present() {
1068 let tmp_dir = tempdir().unwrap();
1069 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1070
1071 let plugin_name = "Blank.esp";
1072 deactivate(&mut load_order, plugin_name).unwrap();
1073
1074 assert!(!load_order.is_active(plugin_name));
1075
1076 activate(&mut load_order, plugin_name).unwrap();
1077
1078 assert!(load_order.is_active(plugin_name));
1079 assert!(load_order
1080 .find_plugin(plugin_name)
1081 .unwrap()
1082 .is_explicitly_active());
1083 }
1084
1085 #[test]
1086 fn activate_with_blueprint_ship_base_plugin_should_count_activating_blueprint_ships_plugin() {
1087 let tmp_dir = tempdir().unwrap();
1088 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1089
1090 let plugin_name = "Blank.esp";
1091 deactivate(&mut load_order, plugin_name).unwrap();
1092
1093 let plugins = prepare_bulk_full_plugins(&mut load_order);
1094 for plugin in &plugins[..254] {
1095 activate(&mut load_order, plugin).unwrap();
1096 }
1097
1098 let blueprint_ships = "BlueprintShips-Blank.esm";
1099 copy_to_test_dir(
1100 "Blank.full.esm",
1101 blueprint_ships,
1102 load_order.game_settings(),
1103 );
1104 add(&mut load_order, blueprint_ships).unwrap();
1105
1106 assert!(!load_order.is_active(plugin_name));
1107 assert!(!load_order.is_active(blueprint_ships));
1108
1109 let err = activate(&mut load_order, plugin_name).unwrap_err();
1110
1111 match err {
1112 Error::TooManyActivePlugins {
1113 light_count,
1114 medium_count,
1115 full_count,
1116 } => {
1117 assert_eq!(0, light_count);
1118 assert_eq!(0, medium_count);
1119 assert_eq!(256, full_count);
1120 }
1121 e => panic!("Unexpected error type: {e:?}"),
1122 }
1123 }
1124
1125 #[test]
1126 fn deactivate_should_deactivate_the_plugin_with_the_given_filename() {
1127 let tmp_dir = tempdir().unwrap();
1128 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1129
1130 assert!(load_order.is_active("Blank.esp"));
1131 assert!(deactivate(&mut load_order, "Blank.esp").is_ok());
1132 assert!(!load_order.is_active("Blank.esp"));
1133 }
1134
1135 #[test]
1136 fn deactivate_should_error_if_the_plugin_is_not_in_the_load_order() {
1137 let tmp_dir = tempdir().unwrap();
1138 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1139
1140 assert!(deactivate(&mut load_order, "missing.esp").is_err());
1141 assert!(load_order.index_of("missing.esp").is_none());
1142 }
1143
1144 #[test]
1145 fn deactivate_should_error_if_given_an_implicitly_active_plugin() {
1146 let tmp_dir = tempdir().unwrap();
1147 let mut load_order = prepare(GameId::SkyrimSE, tmp_dir.path());
1148
1149 prepend_early_loader(&mut load_order);
1150
1151 assert!(activate(&mut load_order, "Skyrim.esm").is_ok());
1152 assert!(deactivate(&mut load_order, "Skyrim.esm").is_err());
1153 assert!(load_order.is_active("Skyrim.esm"));
1154 }
1155
1156 #[test]
1157 fn deactivate_should_error_if_given_a_missing_implicitly_active_plugin() {
1158 let tmp_dir = tempdir().unwrap();
1159 let mut load_order = prepare(GameId::Skyrim, tmp_dir.path());
1160
1161 assert!(deactivate(&mut load_order, "Update.esm").is_err());
1162 assert!(load_order.index_of("Update.esm").is_none());
1163 }
1164
1165 #[test]
1166 fn deactivate_should_error_if_given_a_blueprint_ships_plugin_that_is_implicitly_activated_by_another_plugin(
1167 ) {
1168 let tmp_dir = tempdir().unwrap();
1169 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1170
1171 let blueprint_ships = "BlueprintShips-Blank.esm";
1172 copy_to_test_dir(
1173 "Blank.full.esm",
1174 blueprint_ships,
1175 load_order.game_settings(),
1176 );
1177 add(&mut load_order, blueprint_ships).unwrap();
1178 activate(&mut load_order, blueprint_ships).unwrap();
1179
1180 let err = deactivate(&mut load_order, blueprint_ships).unwrap_err();
1181
1182 match err {
1183 Error::ImplicitlyActivePlugin(p) => assert_eq!(blueprint_ships, p),
1184 e => panic!("Unexpected error type: {e:?}"),
1185 }
1186
1187 assert!(load_order.is_active(blueprint_ships));
1188 }
1189
1190 #[test]
1191 fn deactivate_should_succeed_if_given_a_blueprint_ships_plugin_with_an_inactive_base_plugin() {
1192 let tmp_dir = tempdir().unwrap();
1193 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1194
1195 add(&mut load_order, "Blank.full.esm").unwrap();
1196
1197 let blueprint_ships = "BlueprintShips-Blank.full.esm";
1198 copy_to_test_dir(
1199 "Blank.full.esm",
1200 blueprint_ships,
1201 load_order.game_settings(),
1202 );
1203 add(&mut load_order, blueprint_ships).unwrap();
1204 activate(&mut load_order, blueprint_ships).unwrap();
1205
1206 deactivate(&mut load_order, blueprint_ships).unwrap();
1207
1208 assert!(!load_order.is_active(blueprint_ships));
1209 }
1210
1211 #[test]
1212 fn deactivate_should_succeed_if_given_a_blueprint_ships_plugin_that_is_not_implicitly_activated_by_another_plugin(
1213 ) {
1214 let tmp_dir = tempdir().unwrap();
1215 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1216
1217 let blueprint_ships = "BlueprintShips-A.esm";
1218 copy_to_test_dir(
1219 "Blank.full.esm",
1220 blueprint_ships,
1221 load_order.game_settings(),
1222 );
1223 add(&mut load_order, blueprint_ships).unwrap();
1224 activate(&mut load_order, blueprint_ships).unwrap();
1225
1226 deactivate(&mut load_order, blueprint_ships).unwrap();
1227
1228 assert!(!load_order.is_active(blueprint_ships));
1229 }
1230
1231 #[test]
1232 fn deactivate_should_deactivate_a_related_implicitly_active_blueprint_ships_plugin() {
1233 let tmp_dir = tempdir().unwrap();
1234 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1235
1236 let blueprint_ships = "BlueprintShips-Blank.esm";
1237 copy_to_test_dir(
1238 "Blank.full.esm",
1239 blueprint_ships,
1240 load_order.game_settings(),
1241 );
1242 let index = add(&mut load_order, blueprint_ships).unwrap();
1243 load_order.plugins[index].implicitly_activate().unwrap();
1244
1245 let plugin_name = "Blank.esp";
1246 assert!(load_order.is_active(plugin_name));
1247 assert!(load_order.is_active(blueprint_ships));
1248
1249 deactivate(&mut load_order, plugin_name).unwrap();
1250
1251 assert!(!load_order.is_active(plugin_name));
1252 assert!(!load_order.is_active(blueprint_ships));
1253 }
1254
1255 #[test]
1256 fn deactivate_should_not_deactivate_a_related_blueprint_ships_that_is_explicitly_active() {
1257 let tmp_dir = tempdir().unwrap();
1258 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1259
1260 let blueprint_ships = "BlueprintShips-Blank.esm";
1261 copy_to_test_dir(
1262 "Blank.full.esm",
1263 blueprint_ships,
1264 load_order.game_settings(),
1265 );
1266 add(&mut load_order, blueprint_ships).unwrap();
1267 activate(&mut load_order, blueprint_ships).unwrap();
1268
1269 let plugin_name = "Blank.esp";
1270 assert!(load_order.is_active(plugin_name));
1271 assert!(load_order.is_active(blueprint_ships));
1272
1273 deactivate(&mut load_order, plugin_name).unwrap();
1274
1275 assert!(!load_order.is_active(plugin_name));
1276 assert!(load_order.is_active(blueprint_ships));
1277 }
1278
1279 #[test]
1280 fn deactivate_should_not_deactivate_a_related_blueprint_ships_that_is_implicitly_active_due_to_another_plugin(
1281 ) {
1282 let tmp_dir = tempdir().unwrap();
1283 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1284
1285 let plugin_name = "Blank.esm";
1286 copy_to_test_dir("Blank.full.esm", plugin_name, load_order.game_settings());
1287 add(&mut load_order, plugin_name).unwrap();
1288 activate(&mut load_order, plugin_name).unwrap();
1289
1290 let blueprint_ships = "BlueprintShips-Blank.esm";
1291 copy_to_test_dir(
1292 "Blank.full.esm",
1293 blueprint_ships,
1294 load_order.game_settings(),
1295 );
1296 let index = add(&mut load_order, blueprint_ships).unwrap();
1297 load_order.plugins[index].implicitly_activate().unwrap();
1298
1299 let plugin_name = "Blank.esp";
1300 assert!(load_order.is_active(plugin_name));
1301 assert!(load_order.is_active(blueprint_ships));
1302
1303 deactivate(&mut load_order, plugin_name).unwrap();
1304
1305 assert!(!load_order.is_active(plugin_name));
1306 assert!(load_order.is_active(blueprint_ships));
1307 }
1308
1309 #[test]
1310 fn deactivate_should_not_deactivate_a_related_blueprint_ships_that_is_implicitly_active_due_to_game_settings(
1311 ) {
1312 let tmp_dir = tempdir().unwrap();
1313 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1314
1315 let blueprint_ships = "BlueprintShips-Blank.esm";
1316 copy_to_test_dir(
1317 "Blank.full.esm",
1318 blueprint_ships,
1319 load_order.game_settings(),
1320 );
1321 let index = add(&mut load_order, blueprint_ships).unwrap();
1322 load_order.plugins[index].implicitly_activate().unwrap();
1323
1324 std::fs::write(
1325 load_order
1326 .game_settings
1327 .plugins_directory()
1328 .parent()
1329 .unwrap()
1330 .join("Starfield.ccc"),
1331 blueprint_ships,
1332 )
1333 .unwrap();
1334 load_order
1335 .game_settings
1336 .refresh_implicitly_active_plugins()
1337 .unwrap();
1338
1339 let plugin_name = "Blank.esp";
1340 assert!(load_order.is_active(plugin_name));
1341 assert!(load_order.is_active(blueprint_ships));
1342
1343 deactivate(&mut load_order, plugin_name).unwrap();
1344
1345 assert!(!load_order.is_active(plugin_name));
1346 assert!(load_order.is_active(blueprint_ships));
1347 }
1348
1349 #[test]
1350 fn deactivate_should_do_nothing_if_the_plugin_is_inactive() {
1351 let tmp_dir = tempdir().unwrap();
1352 let mut load_order = prepare(GameId::Skyrim, tmp_dir.path());
1353
1354 assert!(!load_order.is_active("Blank - Different.esp"));
1355 assert!(deactivate(&mut load_order, "Blank - Different.esp").is_ok());
1356 assert!(!load_order.is_active("Blank - Different.esp"));
1357 }
1358
1359 #[test]
1360 fn set_active_plugins_should_error_if_passed_an_invalid_plugin_name() {
1361 let tmp_dir = tempdir().unwrap();
1362 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1363
1364 let active_plugins = ["missing.esp"];
1365 assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1366 assert_eq!(1, load_order.active_plugin_names().len());
1367 }
1368
1369 #[test]
1370 fn set_active_plugins_should_error_if_the_given_plugins_are_missing_implicitly_active_plugins()
1371 {
1372 let tmp_dir = tempdir().unwrap();
1373 let mut load_order = prepare(GameId::SkyrimSE, tmp_dir.path());
1374
1375 prepend_early_loader(&mut load_order);
1376
1377 let active_plugins = ["Blank.esp"];
1378 assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1379 assert_eq!(1, load_order.active_plugin_names().len());
1380 }
1381
1382 #[test]
1383 fn set_active_plugins_should_error_if_a_missing_implicitly_active_plugin_is_given() {
1384 let tmp_dir = tempdir().unwrap();
1385 let mut load_order = prepare(GameId::Skyrim, tmp_dir.path());
1386
1387 let active_plugins = ["Update.esm", "Blank.esp"];
1388 assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1389 assert_eq!(1, load_order.active_plugin_names().len());
1390 }
1391
1392 #[test]
1393 fn set_active_plugins_should_error_if_given_plugins_not_in_the_load_order() {
1394 let tmp_dir = tempdir().unwrap();
1395 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1396
1397 let active_plugins = ["Blank - Master Dependent.esp", NON_ASCII];
1398 assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1399 assert!(!load_order.is_active("Blank - Master Dependent.esp"));
1400 assert!(load_order
1401 .index_of("Blank - Master Dependent.esp")
1402 .is_none());
1403 assert!(!load_order.is_active(NON_ASCII));
1404 assert!(load_order.index_of(NON_ASCII).is_none());
1405 }
1406
1407 #[test]
1408 fn set_active_plugins_should_deactivate_all_plugins_not_given() {
1409 let tmp_dir = tempdir().unwrap();
1410 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1411
1412 let active_plugins = ["Blank - Different.esp"];
1413 assert!(load_order.is_active("Blank.esp"));
1414 assert!(set_active_plugins(&mut load_order, &active_plugins).is_ok());
1415 assert!(!load_order.is_active("Blank.esp"));
1416 }
1417
1418 #[test]
1419 fn set_active_plugins_should_activate_all_given_plugins() {
1420 let tmp_dir = tempdir().unwrap();
1421 let mut load_order = prepare(GameId::Oblivion, tmp_dir.path());
1422
1423 let active_plugins = ["Blank - Different.esp"];
1424 assert!(!load_order.is_active("Blank - Different.esp"));
1425 assert!(set_active_plugins(&mut load_order, &active_plugins).is_ok());
1426 assert!(load_order.is_active("Blank - Different.esp"));
1427 }
1428
1429 #[test]
1430 fn set_active_plugins_should_count_update_plugins_towards_limit() {
1431 let tmp_dir = tempdir().unwrap();
1432 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1433
1434 let blank_override = "Blank - Override.esp";
1435 load_and_insert(&mut load_order, blank_override);
1436
1437 let mut active_plugins = vec![blank_override.to_owned()];
1438
1439 let plugins = prepare_bulk_full_plugins(&mut load_order);
1440 for plugin in plugins.into_iter().take(255) {
1441 active_plugins.push(plugin);
1442 }
1443
1444 let active_plugins: Vec<&str> = active_plugins
1445 .iter()
1446 .map(std::string::String::as_str)
1447 .collect();
1448
1449 assert!(set_active_plugins(&mut load_order, &active_plugins).is_err());
1450 assert_eq!(1, load_order.active_plugin_names().len());
1451 }
1452
1453 #[test]
1454 fn set_active_plugins_should_lower_the_full_plugin_limit_if_a_light_plugin_is_present() {
1455 let tmp_dir = tempdir().unwrap();
1456 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1457
1458 let full = prepare_bulk_full_plugins(&mut load_order);
1459
1460 let plugin = "Blank.small.esm";
1461 load_and_insert(&mut load_order, plugin);
1462
1463 let mut plugin_refs = vec![plugin];
1464 plugin_refs.extend(full[..254].iter().map(String::as_str));
1465
1466 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1467 assert_eq!(255, load_order.active_plugin_names().len());
1468
1469 plugin_refs.push(full[254].as_str());
1470
1471 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1472 assert_eq!(255, load_order.active_plugin_names().len());
1473 }
1474
1475 #[test]
1476 fn set_active_plugins_should_lower_the_full_plugin_limit_if_a_medium_plugin_is_present() {
1477 let tmp_dir = tempdir().unwrap();
1478 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1479
1480 let full = prepare_bulk_full_plugins(&mut load_order);
1481
1482 let plugin = "Blank.medium.esm";
1483 load_and_insert(&mut load_order, plugin);
1484
1485 let mut plugin_refs = vec![plugin];
1486 plugin_refs.extend(full[..254].iter().map(String::as_str));
1487
1488 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1489 assert_eq!(255, load_order.active_plugin_names().len());
1490
1491 plugin_refs.push(full[254].as_str());
1492
1493 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1494 assert_eq!(255, load_order.active_plugin_names().len());
1495 }
1496
1497 #[test]
1498 fn set_active_plugins_should_lower_the_full_plugin_limit_if_light_and_plugins_are_present() {
1499 let tmp_dir = tempdir().unwrap();
1500 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1501
1502 let full = prepare_bulk_full_plugins(&mut load_order);
1503
1504 let medium_plugin = "Blank.medium.esm";
1505 let light_plugin = "Blank.small.esm";
1506 load_and_insert(&mut load_order, medium_plugin);
1507 load_and_insert(&mut load_order, light_plugin);
1508
1509 let mut plugin_refs = vec![medium_plugin, light_plugin];
1510 plugin_refs.extend(full[..253].iter().map(String::as_str));
1511
1512 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1513 assert_eq!(255, load_order.active_plugin_names().len());
1514
1515 plugin_refs.push(full[253].as_str());
1516
1517 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1518 assert_eq!(255, load_order.active_plugin_names().len());
1519 }
1520
1521 #[test]
1522 fn set_active_plugins_should_count_full_medium_and_small_plugins_separately() {
1523 let tmp_dir = tempdir().unwrap();
1524 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1525
1526 let full = prepare_bulk_full_plugins(&mut load_order);
1527 let medium = prepare_bulk_medium_plugins(&mut load_order);
1528 let light = prepare_bulk_light_plugins(&mut load_order);
1529
1530 let mut plugin_refs = Vec::with_capacity(4064);
1531 plugin_refs.extend(full[..252].iter().map(String::as_str));
1532 plugin_refs.extend(medium[..256].iter().map(String::as_str));
1533 plugin_refs.extend(light[..4096].iter().map(String::as_str));
1534
1535 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_ok());
1536 assert_eq!(4604, load_order.active_plugin_names().len());
1537 }
1538
1539 #[test]
1540 fn set_active_plugins_should_error_if_given_more_than_254_full_plugins() {
1541 let tmp_dir = tempdir().unwrap();
1542 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1543
1544 let full = prepare_bulk_full_plugins(&mut load_order);
1545
1546 let plugin_refs: Vec<_> = full[..256].iter().map(String::as_str).collect();
1547
1548 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1549 assert_eq!(1, load_order.active_plugin_names().len());
1550 }
1551
1552 #[test]
1553 fn set_active_plugins_should_error_if_given_more_than_256_medium_plugins() {
1554 let tmp_dir = tempdir().unwrap();
1555 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1556
1557 let medium = prepare_bulk_medium_plugins(&mut load_order);
1558
1559 let plugin_refs: Vec<_> = medium[..257].iter().map(String::as_str).collect();
1560
1561 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1562 assert_eq!(1, load_order.active_plugin_names().len());
1563 }
1564
1565 #[test]
1566 fn set_active_plugins_should_error_if_given_more_than_4096_light_plugins() {
1567 let tmp_dir = tempdir().unwrap();
1568 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1569
1570 let light = prepare_bulk_light_plugins(&mut load_order);
1571
1572 let plugin_refs: Vec<_> = light[..4097].iter().map(String::as_str).collect();
1573
1574 assert!(set_active_plugins(&mut load_order, &plugin_refs).is_err());
1575 assert_eq!(1, load_order.active_plugin_names().len());
1576 }
1577
1578 #[test]
1579 fn set_active_plugins_should_error_if_an_implicitly_active_blueprint_ships_plugin_is_not_given()
1580 {
1581 let tmp_dir = tempdir().unwrap();
1582 let mut load_order = prepare(GameId::Starfield, tmp_dir.path());
1583
1584 let blueprint_ships = "BlueprintShips-Blank.esm";
1585 copy_to_test_dir(
1586 "Blank.full.esm",
1587 blueprint_ships,
1588 load_order.game_settings(),
1589 );
1590 add(&mut load_order, blueprint_ships).unwrap();
1591
1592 let err = set_active_plugins(&mut load_order, &["Blank.esp"]).unwrap_err();
1593
1594 match err {
1595 Error::ImplicitlyActivePlugin(n) => assert_eq!(blueprint_ships, n),
1596 e => panic!("Unexpected error type: {e:?}"),
1597 }
1598 }
1599}