Support reading file version from Windows executables

This commit is contained in:
Oliver Hamlet
2018-10-05 19:52:51 +01:00
parent 2def44d3aa
commit be313c40cb
5 changed files with 87 additions and 6 deletions
+1
View File
@@ -3,3 +3,4 @@
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
/testing-plugins /testing-plugins
/loot_api*
+1
View File
@@ -6,6 +6,7 @@ authors = ["Oliver Hamlet <oliver.hamlet@gmail.com>"]
[dependencies] [dependencies]
crc = "1.0.0" crc = "1.0.0"
nom = "4.0.0" nom = "4.0.0"
pelite = "0.7.0"
regex = "1.0.5" regex = "1.0.5"
[dev-dependencies] [dev-dependencies]
+2 -1
View File
@@ -21,4 +21,5 @@ Currently only condition parsing is complete. Evaluation is partially done, the
rest hasn't yet been started. rest hasn't yet been started.
The tests need the [testing-plugins](https://github.com/WrinklyNinja/testing-plugins) 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.
+8 -2
View File
@@ -1,6 +1,7 @@
extern crate crc; extern crate crc;
#[macro_use] #[macro_use]
extern crate nom; extern crate nom;
extern crate pelite;
extern crate regex; extern crate regex;
#[cfg(test)] #[cfg(test)]
@@ -24,8 +25,7 @@ use function::Function;
pub enum Error { pub enum Error {
ParsingIncomplete, ParsingIncomplete,
ParsingError, ParsingError,
InvalidPath(PathBuf), PeParsingError,
InvalidRegex(String),
IoError(io::Error), IoError(io::Error),
} }
@@ -44,6 +44,12 @@ impl From<io::Error> for Error {
} }
} }
impl From<pelite::resources::FindError> for Error {
fn from(_: pelite::resources::FindError) -> Self {
Error::PeParsingError
}
}
pub enum GameType { pub enum GameType {
Tes4, Tes4,
Tes5, Tes5,
+75 -3
View File
@@ -1,6 +1,10 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::path::Path; use std::path::Path;
use pelite::resources::version_info::VersionInfo;
use pelite::resources::FindError;
use pelite::FileMap;
use Error; use Error;
#[derive(Clone, Debug, PartialEq, PartialOrd)] #[derive(Clone, Debug, PartialEq, PartialOrd)]
@@ -25,8 +29,40 @@ pub struct Version {
impl Version { impl Version {
pub fn read_file_version(file_path: &Path) -> Result<Self, Error> { pub fn read_file_version(file_path: &Path) -> Result<Self, Error> {
// TODO: Actually read the file's File Version field. let file_map = FileMap::open(file_path)?;
Ok(Version::from(format!("{}", file_path.display()).as_str())) 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<VersionInfo, FindError> {
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 { Version {
release_ids: release.split('.').map(Identifier::from).collect(), release_ids: release.split('.').map(Identifier::from).collect(),
pre_release_ids: pre_release pre_release_ids: pre_release
.split(is_pre_release_separator) .split_terminator(is_pre_release_separator)
.map(Identifier::from) .map(Identifier::from)
.collect(), .collect(),
} }
@@ -108,6 +144,42 @@ mod tests {
mod empty { mod empty {
use super::super::*; 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] #[test]
fn version_eq_an_empty_string_should_equal_an_empty_string() { fn version_eq_an_empty_string_should_equal_an_empty_string() {
assert_eq!(Version::from(""), Version::from("")); assert_eq!(Version::from(""), Version::from(""));