diff --git a/.gitignore b/.gitignore index d222fd8..a68598c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/*.rs.bk Cargo.lock /testing-plugins +/loot_api* diff --git a/Cargo.toml b/Cargo.toml index 1cd0930..473d272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Oliver Hamlet "] [dependencies] crc = "1.0.0" nom = "4.0.0" +pelite = "0.7.0" regex = "1.0.5" [dev-dependencies] diff --git a/README.md b/README.md index 72f6318..0ecd869 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,5 @@ Currently only condition parsing is complete. Evaluation is partially done, the rest hasn't yet been started. The tests need the [testing-plugins](https://github.com/WrinklyNinja/testing-plugins) -directory to be present in the repo root. +and the [LOOT API v0.13.8](https://github.com/loot/loot-api/releases/tag/0.13.8) +Windows archives to be extracted and present in the repo root. diff --git a/src/lib.rs b/src/lib.rs index 47e187a..660e015 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ extern crate crc; #[macro_use] extern crate nom; +extern crate pelite; extern crate regex; #[cfg(test)] @@ -24,8 +25,7 @@ use function::Function; pub enum Error { ParsingIncomplete, ParsingError, - InvalidPath(PathBuf), - InvalidRegex(String), + PeParsingError, IoError(io::Error), } @@ -44,6 +44,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: pelite::resources::FindError) -> Self { + Error::PeParsingError + } +} + pub enum GameType { Tes4, Tes5, diff --git a/src/version.rs b/src/version.rs index 102ac63..b869af4 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,6 +1,10 @@ use std::cmp::Ordering; use std::path::Path; +use pelite::resources::version_info::VersionInfo; +use pelite::resources::FindError; +use pelite::FileMap; + use Error; #[derive(Clone, Debug, PartialEq, PartialOrd)] @@ -25,8 +29,40 @@ pub struct Version { impl Version { pub fn read_file_version(file_path: &Path) -> Result { - // TODO: Actually read the file's File Version field. - Ok(Version::from(format!("{}", file_path.display()).as_str())) + let file_map = FileMap::open(file_path)?; + let version_info = get_pe_version_info(file_map.as_ref())?; + + if let Some(fixed_file_info) = version_info.fixed() { + let version = format!( + "{}.{}.{}.{}", + fixed_file_info.dwFileVersion.Major, + fixed_file_info.dwFileVersion.Minor, + fixed_file_info.dwFileVersion.Patch, + fixed_file_info.dwFileVersion.Build + ); + + Ok(Version::from(version.as_str())) + } else { + Ok(Version::from("")) + } + } +} + +fn get_pe_version_info(bytes: &[u8]) -> Result { + use pelite; + use pelite::pe64; + match pe64::PeFile::from_bytes(bytes) { + Ok(file) => { + use pelite::pe64::Pe; + + file.resources()?.version_info() + } + Err(pelite::Error::PeMagic) => { + use pelite::pe32::{Pe, PeFile}; + + PeFile::from_bytes(bytes)?.resources()?.version_info() + } + Err(e) => Err(e.into()), } } @@ -50,7 +86,7 @@ impl<'a> From<&'a str> for Version { Version { release_ids: release.split('.').map(Identifier::from).collect(), pre_release_ids: pre_release - .split(is_pre_release_separator) + .split_terminator(is_pre_release_separator) .map(Identifier::from) .collect(), } @@ -108,6 +144,42 @@ mod tests { mod empty { use super::super::*; + #[test] + fn version_read_file_version_should_read_the_file_version_field_of_a_32_bit_executable() { + let version = Version::read_file_version(Path::new( + "loot_api-0.13.8-0-g47797cc_dev-win32/loot_api.dll", + )).unwrap(); + + assert_eq!( + version.release_ids, + vec![ + Identifier::Numeric(0), + Identifier::Numeric(13), + Identifier::Numeric(8), + Identifier::Numeric(0), + ] + ); + assert!(version.pre_release_ids.is_empty()); + } + + #[test] + fn version_read_file_version_should_read_the_file_version_field_of_a_64_bit_executable() { + let version = Version::read_file_version(Path::new( + "loot_api-0.13.8-0-g47797cc_dev-win64/loot_api.dll", + )).unwrap(); + + assert_eq!( + version.release_ids, + vec![ + Identifier::Numeric(0), + Identifier::Numeric(13), + Identifier::Numeric(8), + Identifier::Numeric(0), + ] + ); + assert!(version.pre_release_ids.is_empty()); + } + #[test] fn version_eq_an_empty_string_should_equal_an_empty_string() { assert_eq!(Version::from(""), Version::from(""));