Skip to content

Commit

Permalink
Support .nam files for Fallout: New Vegas
Browse files Browse the repository at this point in the history
If a .nam file is present in the Data folder, any .esp or .esm file with
the (case-insensitively) same basename will be activated by the
game. Only Fallout NV uses .nam files like this.
  • Loading branch information
Ortham committed Sep 23, 2023
1 parent cd1dc59 commit 8d233ed
Showing 1 changed file with 82 additions and 1 deletion.
83 changes: 82 additions & 1 deletion src/game_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,44 @@ fn hardcoded_plugins(game_id: GameId) -> &'static [&'static str] {
}
}

fn find_nam_plugins(plugins_path: &Path) -> Result<Vec<String>, Error> {
// Scan the path for .nam files. Each .nam file can activate a .esm or .esp
// plugin with the same basename, so return those filenames.
let mut plugin_names = Vec::new();

if !plugins_path.exists() {
return Ok(plugin_names);
}

let dir_iter = plugins_path
.read_dir()?
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().map(|f| f.is_file()).unwrap_or(false))
.filter(|e| {
e.path()
.extension()
.unwrap_or_default()
.eq_ignore_ascii_case("nam")
});

for entry in dir_iter {
let file_name = entry.file_name();

let esp = Path::new(&file_name).with_extension("esp");
if let Some(esp) = esp.to_str() {
plugin_names.push(esp.to_string());
}

let esm = Path::new(&file_name).with_extension("esm");
if let Some(esm) = esm.to_str() {
plugin_names.push(esm.to_string());
}
}

Ok(plugin_names)
}

fn implicitly_active_plugins(game_id: GameId, game_path: &Path) -> Result<Vec<String>, Error> {
let mut plugin_names: Vec<String> = hardcoded_plugins(game_id)
.iter()
Expand All @@ -367,14 +405,23 @@ fn implicitly_active_plugins(game_id: GameId, game_path: &Path) -> Result<Vec<St
}
}

if game_id == GameId::FalloutNV {
// If there is a .nam file with the same basename as a plugin then the plugin is activated
// and listed as a DLC in the game's title screen menu. This only works in the game's
// Data path, so ignore additional plugin directories.
let nam_plugins = find_nam_plugins(&game_path.join("Data"))?;

plugin_names.extend(nam_plugins);
}

Ok(plugin_names)
}

#[cfg(test)]
mod tests {
#[cfg(windows)]
use std::env;
use std::io::Write;
use std::{fs::create_dir, io::Write};
use tempfile::tempdir;

use crate::tests::copy_to_dir;
Expand Down Expand Up @@ -1001,6 +1048,40 @@ mod tests {
assert_eq!(plugins, settings.implicitly_active_plugins());
}

#[test]
fn implicitly_active_plugins_should_include_plugins_with_nam_files_for_fallout_nv() {
let tmp_dir = tempdir().unwrap();
let game_path = tmp_dir.path();
let data_path = game_path.join("Data");

create_dir(&data_path).unwrap();
File::create(data_path.join("plugin1.nam")).unwrap();
File::create(data_path.join("plugin2.NAM")).unwrap();

let settings =
GameSettings::with_local_path(GameId::FalloutNV, &game_path, &PathBuf::default())
.unwrap();

let plugins = vec!["plugin1.esp", "plugin1.esm", "plugin2.esp", "plugin2.esm"];
assert_eq!(plugins, settings.implicitly_active_plugins());
}

#[test]
fn implicitly_active_plugins_should_include_plugins_with_nam_files_for_games_other_than_fallout_nv(
) {
let tmp_dir = tempdir().unwrap();
let game_path = tmp_dir.path();
let data_path = game_path.join("Data");

create_dir(&data_path).unwrap();
File::create(data_path.join("plugin.nam")).unwrap();

let settings =
GameSettings::with_local_path(GameId::Fallout3, &game_path, &PathBuf::default())
.unwrap();
assert!(settings.implicitly_active_plugins().is_empty());
}

#[test]
fn is_implicitly_active_should_return_true_iff_the_plugin_is_implicitly_active() {
let settings =
Expand Down

0 comments on commit 8d233ed

Please sign in to comment.