Skip to content

Commit

Permalink
Add default support for Starfield's My Games path
Browse files Browse the repository at this point in the history
  • Loading branch information
Ortham committed Sep 7, 2023
1 parent 4f33c94 commit 40a2d8d
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ unicase = "2.0.0"
rayon = "1.0.0"

[target.'cfg(windows)'.dependencies]
app_dirs2 = "2.3"
dirs = "5.0"

[dev-dependencies]
criterion = "0.5.1"
Expand Down
10 changes: 0 additions & 10 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,6 @@ pub enum Error {
InstalledPlugin(String),
}

#[cfg(windows)]
impl From<app_dirs2::AppDirsError> for Error {
fn from(error: app_dirs2::AppDirsError) -> Self {
match error {
app_dirs2::AppDirsError::Io(x) => Error::IoError(x),
_ => Error::NoLocalAppData,
}
}
}

impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::IoError(error)
Expand Down
103 changes: 92 additions & 11 deletions src/game_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ const MS_FO4_VAULT_TEC_PATH: &str = "../../Fallout 4- Vault-Tec Workshop (PC)/Co
impl GameSettings {
#[cfg(windows)]
pub fn new(game_id: GameId, game_path: &Path) -> Result<GameSettings, Error> {
let local_app_data_path = app_dirs2::get_data_root(app_dirs2::AppDataType::UserCache)?;
let local_app_data_path = match dirs::data_local_dir() {
Some(x) => x,
None => return Err(Error::NoLocalAppData),
};
let local_path = match appdata_folder_name(game_id, game_path) {
Some(x) => local_app_data_path.join(x),
None => local_app_data_path,
Expand Down Expand Up @@ -126,7 +129,9 @@ impl GameSettings {
plugins_file_path,
load_order_path,
implicitly_active_plugins,
additional_plugins_directories: additional_plugins_directories(game_id, game_path),
additional_plugins_directories: additional_plugins_directories(
game_id, game_path, local_path,
)?,
})
}

Expand Down Expand Up @@ -282,19 +287,58 @@ fn is_microsoft_store_install(game_id: GameId, game_path: &Path) -> bool {
}
}

fn additional_plugins_directories(game_id: GameId, game_path: &Path) -> Vec<PathBuf> {
#[cfg(windows)]
fn documents_path(_local_path: &Path) -> Option<PathBuf> {
dirs::document_dir()
}

#[cfg(not(windows))]
fn documents_path_without_fallback(local_path: &Path) -> Option<PathBuf> {
local_path
.parent()
.and_then(Path::parent)
.and_then(Path::parent)
.map(|p| p.join("Documents"))
}

#[cfg(not(windows))]
fn documents_path(local_path: &Path) -> Option<PathBuf> {
// Get the documents path relative to the game's local path, which should end in
// AppData/Local/<Game>.
// If the given path doesn't have enough components, try to canonicalise it, and then try again
// using the canonicalised path. Canonicalisation may fail in valid situations like if the given
// path does not exist, so don't try it first.
documents_path_without_fallback(local_path).or_else(|| {
local_path
.canonicalize()
.ok()
.and_then(|p| documents_path_without_fallback(&p))
})
}

fn additional_plugins_directories(
game_id: GameId,
game_path: &Path,
local_path: &Path,
) -> Result<Vec<PathBuf>, Error> {
if game_id == GameId::Fallout4 && is_microsoft_store_install(game_id, game_path) {
vec![
Ok(vec![
game_path.join(MS_FO4_AUTOMATRON_PATH),
game_path.join(MS_FO4_NUKA_WORLD_PATH),
game_path.join(MS_FO4_WASTELAND_PATH),
game_path.join(MS_FO4_TEXTURE_PACK_PATH),
game_path.join(MS_FO4_VAULT_TEC_PATH),
game_path.join(MS_FO4_FAR_HARBOR_PATH),
game_path.join(MS_FO4_CONTRAPTIONS_PATH),
]
])
} else if game_id == GameId::Starfield {
if let Some(path) = documents_path(local_path) {
Ok(vec![path.join("My Games").join("Starfield").join("Data")])
} else {
Err(Error::InvalidPath(local_path.to_owned()))
}
} else {
Vec::new()
Ok(Vec::new())
}
}

Expand Down Expand Up @@ -1114,20 +1158,33 @@ mod tests {
}

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

File::create(game_path.join("appxmanifest.xml")).unwrap();

let settings =
GameSettings::with_local_path(GameId::SkyrimSE, game_path, Path::new("local")).unwrap();
let game_ids = [
GameId::Morrowind,
GameId::Oblivion,
GameId::Skyrim,
GameId::SkyrimSE,
GameId::SkyrimVR,
GameId::Fallout3,
GameId::FalloutNV,
];

assert!(settings.additional_plugins_directories().is_empty());
for game_id in game_ids {
let settings =
GameSettings::with_local_path(game_id, game_path, Path::new("local")).unwrap();

assert!(settings.additional_plugins_directories().is_empty());
}
}

#[test]
fn additional_plugins_directories_should_be_empty_if_game_is_not_from_the_microsoft_store() {
fn additional_plugins_directories_should_be_empty_if_fallout4_is_not_from_the_microsoft_store()
{
let settings =
GameSettings::with_local_path(GameId::Fallout4, Path::new("game"), Path::new("local"))
.unwrap();
Expand Down Expand Up @@ -1160,6 +1217,30 @@ mod tests {
);
}

#[test]
fn additional_plugins_directories_should_not_be_empty_if_game_is_starfield() {
let settings = GameSettings::with_local_path(
GameId::Starfield,
Path::new("game"),
Path::new("user/AppData/Local/Starfield"),
)
.unwrap();

#[cfg(windows)]
let expected_documents_path = dirs::document_dir().unwrap();

#[cfg(not(windows))]
let expected_documents_path = Path::new("user").join("Documents");

assert_eq!(
vec![expected_documents_path
.join("My Games")
.join("Starfield")
.join("Data")],
settings.additional_plugins_directories()
);
}

#[test]
fn plugin_path_should_append_plugin_name_to_additional_plugin_directory_if_that_path_exists() {
let tmp_dir = tempdir().unwrap();
Expand Down

0 comments on commit 40a2d8d

Please sign in to comment.