1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
#![doc(alias = "oblivion")]
#![doc(alias = "fallout 3")]
#![doc(alias = "fo3")]
#![doc(alias = "fallout new vegas")]
#![doc(alias = "new vegas")]
#![doc(alias = "fnv")]
#![doc(alias = "tes5")]
#![doc(alias = "skyrim")]
#![doc(alias = "sse")]
#![doc(alias = "special edition")]
//! TES IV: Oblivion
//!
//! *"You ... I've seen you... Let me see your face... You are the one from my dreams... Then the stars were right, and this is the day. Gods give me strength."*
//!
//! This format debuted with Oblivion and sunset with Skyrim: SSE. This is the first format to introduce compression, and primarily utilizes zlib/lz4 for this purpose. Unlike other formats, [`tes4`](crate::tes4) utilizes a split architecture where files and directories are tracked as separate paths, rather than combined.
//!
//! # Reading
//! ```rust
//! use ba2::{
//! prelude::*,
//! tes4::{Archive, ArchiveKey, DirectoryKey, FileCompressionOptions},
//! };
//! use std::{fs, path::Path};
//!
//! fn example() -> Option<()> {
//! let path = Path::new("path/to/oblivion/Data/Oblivion - Voices2.bsa");
//! let (archive, meta) = Archive::read(path).ok()?;
//! let file = archive
//! .get(&ArchiveKey::from(b"sound/voice/oblivion.esm/imperial/m"))?
//! .get(&DirectoryKey::from(
//! b"testtoddquest_testtoddhappy_00027fa2_1.mp3",
//! ))?;
//! let mut dst = fs::File::create("happy.mp3").ok()?;
//! let options: FileCompressionOptions = meta.into();
//! file.write(&mut dst, &options).ok()?;
//! Some(())
//! }
//! ```
//!
//! # Writing
//! ```rust
//! use ba2::{
//! prelude::*,
//! tes4::{
//! Archive, ArchiveKey, ArchiveOptions, ArchiveTypes, Directory, DirectoryKey, File, Version,
//! },
//! };
//! use std::fs;
//!
//! fn example() -> Option<()> {
//! let file = File::from_decompressed(b"Hello world!\n");
//! let directory: Directory = [(DirectoryKey::from(b"hello.txt"), file)]
//! .into_iter()
//! .collect();
//! let archive: Archive = [(ArchiveKey::from(b"misc"), directory)]
//! .into_iter()
//! .collect();
//! let mut dst = fs::File::create("example.bsa").ok()?;
//! let options = ArchiveOptions::builder()
//! .types(ArchiveTypes::MISC)
//! .version(Version::SSE)
//! .build();
//! archive.write(&mut dst, &options).ok()?;
//! Some(())
//! }
//! ```
mod archive;
mod directory;
mod file;
mod hashing;
pub use self::{
archive::{
Archive, Flags as ArchiveFlags, Key as ArchiveKey, Options as ArchiveOptions,
OptionsBuilder as ArchiveOptionsBuilder, Types as ArchiveTypes,
},
directory::{Directory, Key as DirectoryKey},
file::{
CompressionOptions as FileCompressionOptions,
CompressionOptionsBuilder as FileCompressionOptionsBuilder, File,
ReadOptions as FileReadOptions, ReadOptionsBuilder as FileReadOptionsBuilder,
},
hashing::{
hash_directory, hash_directory_in_place, hash_file, hash_file_in_place, DirectoryHash,
FileHash, Hash,
},
};
use core::num::TryFromIntError;
use lzzzz::lz4f;
use std::io;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("can not compress the given file because it is already compressed")]
AlreadyCompressed,
#[error("can not decompress the given file because it is already decompressed")]
AlreadyDecompressed,
#[error("buffer failed to decompress to the expected size... expected {expected} bytes, but got {actual} bytes")]
DecompressionSizeMismatch { expected: usize, actual: usize },
#[error("an operation on two integers would have overflowed and corrupted data")]
IntegralOverflow,
#[error("an operation on an integer would have truncated and corrupted data")]
IntegralTruncation,
#[error("invalid size read from archive header: {0}")]
InvalidHeaderSize(u32),
#[error("invalid magic read from archive header: {0}")]
InvalidMagic(u32),
#[error("invalid version read from archive header: {0}")]
InvalidVersion(u32),
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
LZ4(#[from] lz4f::Error),
}
impl From<TryFromIntError> for Error {
fn from(_: TryFromIntError) -> Self {
Self::IntegralTruncation
}
}
pub type Result<T> = core::result::Result<T, Error>;
/// Specifies the codec to use when performing compression/decompression actions on files.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CompressionCodec {
/// The default compression codec.
#[default]
Normal,
//XMem,
}
/// The archive version.
///
/// Each version has an impact on the abi of the TES4 archive file format.
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub enum Version {
#[default]
v103 = 103,
v104 = 104,
v105 = 105,
}
impl Version {
/// The Elder Scrolls IV: Oblivion.
pub const TES4: Self = Self::v103;
/// Fallout 3.
pub const FO3: Self = Self::v104;
/// Fallout: New Vegas.
pub const FNV: Self = Self::v104;
/// The Elder Scrolls V: Skyrim.
pub const TES5: Self = Self::v104;
/// The Elder Scrolls V: Skyrim - Special Edition.
pub const SSE: Self = Self::v105;
}