Skip to content

Commit

Permalink
Refresh implicitly active plugins on load
Browse files Browse the repository at this point in the history
Now that implicitly active plugins are pulled from sources that can't be
reasonably assumed to be unchanging, refresh them on load in case
they've changed since the last time load order data was loaded.

Do it as the first thing so that a failure doesn't erase existing state.
  • Loading branch information
Ortham committed Sep 25, 2023
1 parent c8f6108 commit 9fc693c
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 29 deletions.
128 changes: 104 additions & 24 deletions src/game_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct GameSettings {
id: GameId,
plugins_directory: PathBuf,
plugins_file_path: PathBuf,
my_games_path: PathBuf,
load_order_path: Option<PathBuf>,
implicitly_active_plugins: Vec<String>,
early_loading_plugins: Vec<String>,
Expand Down Expand Up @@ -123,41 +124,35 @@ impl GameSettings {
None => PathBuf::default(),
};

GameSettings::with_local_and_my_games_paths(game_id, game_path, local_path, &my_games_path)
GameSettings::with_local_and_my_games_paths(game_id, game_path, local_path, my_games_path)
}

pub(crate) fn with_local_and_my_games_paths(
game_id: GameId,
game_path: &Path,
local_path: &Path,
my_games_path: &Path,
my_games_path: PathBuf,
) -> Result<GameSettings, Error> {
let plugins_file_path = plugins_file_path(game_id, game_path, local_path)?;
let load_order_path = load_order_path(game_id, local_path);
let plugins_directory = game_path.join(plugins_folder_name(game_id));
let additional_plugins_directories = additional_plugins_directories(game_id, game_path);

let mut test_files = test_files(game_id, game_path, my_games_path)?;

if game_id == GameId::Fallout4 || game_id == GameId::Fallout4VR {
// Fallout 4 ignores plugins.txt and Fallout4.ccc if there are valid plugins listed as test files, so filter out invalid values.
test_files.retain(|f| {
let path = plugin_path(f, &plugins_directory, &additional_plugins_directories);
Plugin::with_path(&path, game_id, false).is_ok()
});
}

let early_loading_plugins =
early_loading_plugins(game_id, game_path, !test_files.is_empty())?;

let implicitly_active_plugins =
implicitly_active_plugins(game_id, game_path, &early_loading_plugins, &test_files)?;
let (early_loading_plugins, implicitly_active_plugins) =
GameSettings::load_implicitly_active_plugins(
game_id,
game_path,
&my_games_path,
&plugins_directory,
&additional_plugins_directories,
)?;

Ok(GameSettings {
id: game_id,
plugins_directory,
plugins_file_path,
load_order_path,
my_games_path,
implicitly_active_plugins,
early_loading_plugins,
additional_plugins_directories,
Expand Down Expand Up @@ -246,6 +241,50 @@ impl GameSettings {
&self.additional_plugins_directories,
)
}

pub fn refresh_implicitly_active_plugins(&mut self) -> Result<(), Error> {
let (early_loading_plugins, implicitly_active_plugins) =
GameSettings::load_implicitly_active_plugins(
self.id,
self.plugins_directory
.parent()
.expect("plugins directory path to have parent path component"),
&self.my_games_path,
&self.plugins_directory,
&self.additional_plugins_directories,
)?;

self.early_loading_plugins = early_loading_plugins;
self.implicitly_active_plugins = implicitly_active_plugins;

Ok(())
}

fn load_implicitly_active_plugins(
game_id: GameId,
game_path: &Path,
my_games_path: &Path,
plugins_directory: &Path,
additional_plugins_directories: &[PathBuf],
) -> Result<(Vec<String>, Vec<String>), Error> {
let mut test_files = test_files(game_id, game_path, my_games_path)?;

if game_id == GameId::Fallout4 || game_id == GameId::Fallout4VR {
// Fallout 4 ignores plugins.txt and Fallout4.ccc if there are valid plugins listed as test files, so filter out invalid values.
test_files.retain(|f| {
let path = plugin_path(f, &plugins_directory, &additional_plugins_directories);
Plugin::with_path(&path, game_id, false).is_ok()
});
}

let early_loading_plugins =
early_loading_plugins(game_id, game_path, !test_files.is_empty())?;

let implicitly_active_plugins =
implicitly_active_plugins(game_id, game_path, &early_loading_plugins, &test_files)?;

Ok((early_loading_plugins, implicitly_active_plugins))
}
}

// The local path can vary depending on where the game was bought from.
Expand Down Expand Up @@ -571,7 +610,7 @@ mod tests {
game_id,
&PathBuf::from("game"),
&PathBuf::from("local"),
&PathBuf::from("my games"),
PathBuf::from("my games"),
)
.unwrap()
}
Expand All @@ -581,7 +620,7 @@ mod tests {
game_id,
game_path,
&PathBuf::default(),
&PathBuf::default(),
PathBuf::default(),
)
.unwrap()
}
Expand Down Expand Up @@ -1053,7 +1092,7 @@ mod tests {
GameId::Fallout4,
game_path,
&PathBuf::default(),
game_path,
game_path.to_path_buf(),
)
.unwrap();

Expand Down Expand Up @@ -1085,7 +1124,7 @@ mod tests {
GameId::SkyrimSE,
game_path,
&PathBuf::default(),
game_path,
game_path.to_path_buf(),
)
.unwrap();

Expand Down Expand Up @@ -1118,7 +1157,7 @@ mod tests {
GameId::Fallout4,
game_path,
&PathBuf::default(),
game_path,
game_path.to_path_buf(),
)
.unwrap();

Expand Down Expand Up @@ -1151,7 +1190,7 @@ mod tests {
GameId::Fallout4VR,
game_path,
&PathBuf::default(),
game_path,
game_path.to_path_buf(),
)
.unwrap();

Expand Down Expand Up @@ -1213,7 +1252,7 @@ mod tests {
GameId::Fallout4,
game_path,
&PathBuf::default(),
game_path,
game_path.to_path_buf(),
)
.unwrap();

Expand Down Expand Up @@ -1366,4 +1405,45 @@ mod tests {
settings.plugin_path(plugin_name)
);
}

#[test]
fn refresh_implicitly_active_plugins_should_update_early_loading_and_implicitly_active_plugins()
{
let tmp_dir = tempdir().unwrap();
let game_path = tmp_dir.path();

let mut settings = GameSettings::with_local_and_my_games_paths(
GameId::SkyrimSE,
game_path,
&PathBuf::default(),
game_path.to_path_buf(),
)
.unwrap();

let hardcoded_plugins = vec![
"Skyrim.esm",
"Update.esm",
"Dawnguard.esm",
"HearthFires.esm",
"Dragonborn.esm",
];
assert_eq!(hardcoded_plugins, settings.early_loading_plugins());
assert_eq!(hardcoded_plugins, settings.implicitly_active_plugins());

std::fs::write(game_path.join("Skyrim.ccc"), "ccBGSSSE002-ExoticArrows.esl").unwrap();
std::fs::write(
game_path.join("Skyrim.ini"),
"[General]\nsTestFile1=plugin.esp\n",
)
.unwrap();

settings.refresh_implicitly_active_plugins().unwrap();

let mut expected_plugins = hardcoded_plugins;
expected_plugins.push("ccBGSSSE002-ExoticArrows.esl");
assert_eq!(expected_plugins, settings.early_loading_plugins());

expected_plugins.push("plugin.esp");
assert_eq!(expected_plugins, settings.implicitly_active_plugins());
}
}
21 changes: 21 additions & 0 deletions src/load_order/asterisk_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ impl WritableLoadOrder for AsteriskBasedLoadOrder {
}

fn load(&mut self) -> Result<(), Error> {
self.game_settings_mut()
.refresh_implicitly_active_plugins()?;

self.plugins_mut().clear();

let plugin_tuples = self.read_from_active_plugins_file()?;
Expand Down Expand Up @@ -888,6 +891,24 @@ mod tests {
);
}

#[test]
fn load_should_refresh_implicitly_active_plugins() {
let tmp_dir = tempdir().unwrap();
let mut load_order = prepare(GameId::SkyrimSE, &tmp_dir.path());

load_order.load().unwrap();

assert!(!load_order.is_active("Blank.esp"));

let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
create_parent_dirs(&ini_path).unwrap();
std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();

load_order.load().unwrap();

assert!(load_order.is_active("Blank.esp"));
}

#[test]
fn save_should_create_active_plugins_file_parent_directory_if_it_does_not_exist() {
let tmp_dir = tempdir().unwrap();
Expand Down
8 changes: 4 additions & 4 deletions src/load_order/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ pub fn game_settings_for_test(game_id: GameId, game_path: &Path) -> GameSettings
let my_games_path = game_path.join("my games");
create_dir_all(&my_games_path).unwrap();

GameSettings::with_local_and_my_games_paths(game_id, game_path, &local_path, &my_games_path)
GameSettings::with_local_and_my_games_paths(game_id, game_path, &local_path, my_games_path)
.unwrap()
}

pub fn mock_game_files(game_id: GameId, game_dir: &Path) -> (GameSettings, Vec<Plugin>) {
let settings = game_settings_for_test(game_id, game_dir);
let mut settings = game_settings_for_test(game_id, game_dir);

copy_to_test_dir("Blank.esm", settings.master_file(), &settings);
copy_to_test_dir("Blank.esm", "Blank.esm", &settings);
Expand All @@ -101,8 +101,8 @@ pub fn mock_game_files(game_id: GameId, game_dir: &Path) -> (GameSettings, Vec<P
);
copy_to_test_dir("Blank.esp", "Blàñk.esp", &settings);

// Recreate settings to account for newly-created plugin files.
let settings = game_settings_for_test(game_id, game_dir);
// Refresh settings to account for newly-created plugin files.
settings.refresh_implicitly_active_plugins().unwrap();

let plugins = vec![
Plugin::new(settings.master_file(), &settings).unwrap(),
Expand Down
21 changes: 21 additions & 0 deletions src/load_order/textfile_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ impl WritableLoadOrder for TextfileBasedLoadOrder {
}

fn load(&mut self) -> Result<(), Error> {
self.game_settings_mut()
.refresh_implicitly_active_plugins()?;

self.plugins_mut().clear();

let load_order_file_exists = self
Expand Down Expand Up @@ -742,6 +745,24 @@ mod tests {
assert_eq!(expected_filenames, load_order.plugin_names());
}

#[test]
fn load_should_refresh_implicitly_active_plugins() {
let tmp_dir = tempdir().unwrap();
let mut load_order = prepare(GameId::Skyrim, &tmp_dir.path());

load_order.load().unwrap();

assert!(!load_order.is_active("Blank.esp"));

let ini_path = tmp_dir.path().join("my games/Skyrim.ini");
create_parent_dirs(&ini_path).unwrap();
std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();

load_order.load().unwrap();

assert!(load_order.is_active("Blank.esp"));
}

#[test]
fn save_should_write_all_plugins_to_load_order_file() {
let tmp_dir = tempdir().unwrap();
Expand Down
21 changes: 21 additions & 0 deletions src/load_order/timestamp_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ impl WritableLoadOrder for TimestampBasedLoadOrder {
}

fn load(&mut self) -> Result<(), Error> {
self.game_settings_mut()
.refresh_implicitly_active_plugins()?;

self.plugins_mut().clear();

self.plugins = self.load_plugins_from_dir();
Expand Down Expand Up @@ -549,6 +552,24 @@ mod tests {
assert_eq!(expected_filenames, load_order.active_plugin_names());
}

#[test]
fn load_should_refresh_implicitly_active_plugins() {
let tmp_dir = tempdir().unwrap();
let mut load_order = prepare(GameId::Oblivion, &tmp_dir.path());

load_order.load().unwrap();

assert!(!load_order.is_active("Blank.esp"));

let ini_path = tmp_dir.path().join("my games/Oblivion.ini");
create_parent_dirs(&ini_path).unwrap();
std::fs::write(&ini_path, "[General]\nsTestFile1=Blank.esp").unwrap();

load_order.load().unwrap();

assert!(load_order.is_active("Blank.esp"));
}

#[test]
fn save_should_preserve_the_existing_set_of_timestamps() {
let tmp_dir = tempdir().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ mod tests {
game_id,
game_path,
&PathBuf::default(),
&PathBuf::default(),
PathBuf::default(),
)
.unwrap()
}
Expand Down

0 comments on commit 9fc693c

Please sign in to comment.