mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Support plugin and archive symlinks
On Linux, and on Windows when the game is OpenMW or Oblivion Remastered.
This commit is contained in:
Generated
+4
-12
@@ -535,16 +535,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libloadorder"
|
||||
version = "18.4.0"
|
||||
version = "18.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc4de694d488b4ce2a775d59825ec30d206546ec87fe0866717e6db25c46a164"
|
||||
checksum = "e3ffd695d1e67468555244807358d53524a372c4f5d2becd0890de48a106e797"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"encoding_rs",
|
||||
"esplugin",
|
||||
"keyvalues-parser",
|
||||
"rayon",
|
||||
"regex",
|
||||
"rust-ini",
|
||||
"unicase",
|
||||
"windows",
|
||||
@@ -1034,13 +1033,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.1"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f"
|
||||
checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
"trim-in-place",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
@@ -1235,12 +1233,6 @@ dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trim-in-place"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
crc32fast = "1.5.0"
|
||||
esplugin = "6.1.3"
|
||||
libloadorder = "18.4.0"
|
||||
libloadorder = "18.5.0"
|
||||
log = { version = "0.4.26", features = ["std"] }
|
||||
loot-condition-interpreter = "5.3.2"
|
||||
petgraph = "0.8.1"
|
||||
|
||||
@@ -482,9 +482,10 @@ private:
|
||||
const std::vector<std::pair<std::string, bool>>& loadOrder) const {
|
||||
if (gameType_ == GameType::tes3) {
|
||||
std::ofstream out(gamePath / "Morrowind.ini");
|
||||
for (const auto& plugin : loadOrder) {
|
||||
if (plugin.second) {
|
||||
out << "GameFile0=" << plugin.first << std::endl;
|
||||
out << "[Game Files]" << std::endl;
|
||||
for (auto i = 0; i < loadOrder.size(); i += 1) {
|
||||
if (loadOrder[i].second) {
|
||||
out << "GameFile" << i << "=" << loadOrder[i].first << std::endl;
|
||||
}
|
||||
}
|
||||
} else if (gameType_ == GameType::openmw) {
|
||||
|
||||
+70
-3
@@ -625,14 +625,15 @@ fn find_archives(
|
||||
data_path: &Path,
|
||||
) -> std::io::Result<Vec<PathBuf>> {
|
||||
let extension = archive_file_extension(game_type);
|
||||
let allow_symlinks = allow_archive_symlinks(game_type);
|
||||
|
||||
let mut archive_paths = Vec::new();
|
||||
for path in additional_data_paths {
|
||||
let paths = find_archives_in_path(path, extension)?;
|
||||
let paths = find_archives_in_path(path, extension, allow_symlinks)?;
|
||||
archive_paths.extend(paths);
|
||||
}
|
||||
|
||||
let paths = find_archives_in_path(data_path, extension)?;
|
||||
let paths = find_archives_in_path(data_path, extension, allow_symlinks)?;
|
||||
archive_paths.extend(paths);
|
||||
|
||||
Ok(archive_paths)
|
||||
@@ -645,18 +646,29 @@ fn archive_file_extension(game_type: GameType) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn allow_archive_symlinks(game_type: GameType) -> bool {
|
||||
// Assuming support for archive symlinks matches support for plugin symlinks.
|
||||
!cfg!(windows) || matches!(game_type, GameType::OblivionRemastered | GameType::OpenMW)
|
||||
}
|
||||
|
||||
fn find_archives_in_path(
|
||||
parent_path: &Path,
|
||||
archive_file_extension: &str,
|
||||
allow_symlinks: bool,
|
||||
) -> std::io::Result<Vec<PathBuf>> {
|
||||
if !parent_path.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::filetype_is_file,
|
||||
reason = "Only files are supported except in specific cases"
|
||||
)]
|
||||
let paths = std::fs::read_dir(parent_path)?
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| {
|
||||
e.file_type().map(|f| f.is_file()).unwrap_or(false)
|
||||
e.file_type()
|
||||
.is_ok_and(|f| f.is_file() || (allow_symlinks && f.is_symlink()))
|
||||
&& iends_with_ascii(&e.file_name().to_string_lossy(), archive_file_extension)
|
||||
})
|
||||
.map(|e| e.path())
|
||||
@@ -2044,6 +2056,61 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mod find_archives {
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn symlink_file(original: &Path, link: &Path) {
|
||||
std::os::windows::fs::symlink_file(original, link).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn symlink_file(original: &Path, link: &Path) {
|
||||
std::os::unix::fs::symlink(original, link).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[parameterized_test(ALL_GAME_TYPES)]
|
||||
fn should_find_symlinked_archives_on_linux(game_type: GameType) {
|
||||
test_find_symlinked_archives(game_type, true);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[parameterized_test(ALL_GAME_TYPES)]
|
||||
fn should_find_symlinked_archives_for_only_openmw_and_oblivion_remastered_on_windows(
|
||||
game_type: GameType,
|
||||
) {
|
||||
test_find_symlinked_archives(
|
||||
game_type,
|
||||
matches!(game_type, GameType::OpenMW | GameType::OblivionRemastered),
|
||||
);
|
||||
}
|
||||
|
||||
fn test_find_symlinked_archives(game_type: GameType, should_include_symlinks: bool) {
|
||||
let tmp_dir = tempdir().unwrap();
|
||||
|
||||
let extension = archive_file_extension(game_type);
|
||||
let archive_path = tmp_dir.path().join(format!("archive{extension}"));
|
||||
let symlink_path = tmp_dir.path().join(format!("archive.symlink{extension}"));
|
||||
|
||||
std::fs::File::create(&archive_path).unwrap();
|
||||
|
||||
symlink_file(&archive_path, &symlink_path);
|
||||
|
||||
let archives = find_archives(game_type, &[], tmp_dir.path()).unwrap();
|
||||
|
||||
assert!(archives.contains(&archive_path));
|
||||
|
||||
if should_include_symlinks {
|
||||
assert!(archives.contains(&symlink_path));
|
||||
} else {
|
||||
assert!(!archives.contains(&symlink_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_plugin_sorting_data_should_filter_out_files_with_false_constraints() {
|
||||
let game_type = GameType::Oblivion;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Allow some lints that are denied at the workspace level.
|
||||
#![allow(
|
||||
clippy::filetype_is_file,
|
||||
clippy::must_use_candidate,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::wildcard_enum_match_arm
|
||||
|
||||
Reference in New Issue
Block a user