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