diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/bsa_ffi/src/archive/tes3_reader.rs | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/bsa_ffi/src/archive/tes3_reader.rs')
| -rw-r--r-- | libs/bsa_ffi/src/archive/tes3_reader.rs | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/libs/bsa_ffi/src/archive/tes3_reader.rs b/libs/bsa_ffi/src/archive/tes3_reader.rs new file mode 100644 index 0000000..c03f155 --- /dev/null +++ b/libs/bsa_ffi/src/archive/tes3_reader.rs @@ -0,0 +1,104 @@ +//! TES3 (Morrowind) BSA reading + +use anyhow::{bail, Context, Result}; +use ba2::tes3::{Archive, File as Tes3File}; +use ba2::{ByteSlice, Reader}; +use rayon::prelude::*; +use std::collections::HashSet; +use std::path::Path; +use std::sync::atomic::{AtomicUsize, Ordering}; +use tracing::debug; + +use super::BsaFileEntry; + +/// List all files in a TES3 (Morrowind) BSA archive +pub fn list_files(bsa_path: &Path) -> Result<Vec<BsaFileEntry>> { + let archive: Archive = Archive::read(bsa_path) + .with_context(|| format!("Failed to open TES3 BSA: {}", bsa_path.display()))?; + + let mut files = Vec::new(); + + for (key, _file) in archive.iter() { + let path = String::from_utf8_lossy(key.name().as_bytes()).to_string(); + + files.push(BsaFileEntry { path }); + } + + debug!( + "Listed {} files in TES3 BSA {}", + files.len(), + bsa_path.display() + ); + Ok(files) +} + +/// Extract a single file from a TES3 (Morrowind) BSA archive +#[allow(dead_code)] +pub fn extract_file(bsa_path: &Path, file_path: &str) -> Result<Vec<u8>> { + let archive: Archive = Archive::read(bsa_path) + .with_context(|| format!("Failed to open TES3 BSA: {}", bsa_path.display()))?; + + // Normalize path separators + let normalized = file_path.replace('/', "\\"); + + // Search case-insensitively + for (key, file) in archive.iter() { + let current_path = String::from_utf8_lossy(key.name().as_bytes()); + + if current_path.eq_ignore_ascii_case(&normalized) { + // TES3 BSAs are uncompressed, so just return the raw bytes + return Ok(file.as_bytes().to_vec()); + } + } + + bail!( + "File not found in TES3 BSA: {} (looking for '{}')", + bsa_path.display(), + file_path + ) +} + +/// Extract multiple files from a TES3 BSA archive in parallel. +/// Opens the archive once, collects matching entries, then writes +/// them in parallel using rayon. +/// `wanted` should contain lowercase backslash-separated paths. +pub fn extract_files_batch<F>( + bsa_path: &Path, + wanted: &HashSet<String>, + callback: F, +) -> Result<usize> +where + F: Fn(&str, Vec<u8>) -> Result<()> + Send + Sync, +{ + let archive: Archive = Archive::read(bsa_path) + .with_context(|| format!("Failed to open TES3 BSA: {}", bsa_path.display()))?; + + // Collect matching entries + let mut entries: Vec<(String, &Tes3File)> = Vec::new(); + for (key, file) in archive.iter() { + let path = String::from_utf8_lossy(key.name().as_bytes()).to_string(); + let lookup = path.to_lowercase(); + if wanted.contains(&lookup) { + entries.push((path, file)); + } + } + + // Write in parallel + let extracted = AtomicUsize::new(0); + entries + .par_iter() + .try_for_each(|(path, file)| -> Result<()> { + callback(path, file.as_bytes().to_vec())?; + extracted.fetch_add(1, Ordering::Relaxed); + Ok(()) + })?; + + let count = extracted.load(Ordering::Relaxed); + debug!( + "Batch extracted {} of {} wanted files from TES3 BSA {}", + count, + wanted.len(), + bsa_path.display() + ); + Ok(count) +} |
