Remove pelite dependency

The advantages of using the built-in implementation instead of pelite are:

- It's much faster on average: for Starfield.exe (~ 100 MB) it is 2.8x
  faster and 4% slower reading file and product versions respectively,
  and for sfse_1_15_222.dll it is 3.35x faster and < 1% slower
  respectively.
- It reduces the transitive dependency count by 8
- It uses ~ 700 lines of first-party code that only depends on the
  standard library, instead of 240715 lines of unaudited third-party code
- pelite hasn't had a release in 3 years, and my PR for replacing winapi
  hasn't had any response in the month and a half it's been open, so the
  built-in implementation is probably less of a maintenance risk.
This commit is contained in:
Oliver Hamlet
2025-12-03 18:39:27 +00:00
parent daf229c1e9
commit 5aa61abc10
4 changed files with 2 additions and 190 deletions
+2 -94
View File
@@ -3,9 +3,6 @@ mod pe;
use std::cmp::Ordering;
use std::path::Path;
use pelite::resources::version_info::VersionInfo;
use pelite::resources::{FindError, Resources};
use crate::error::Error;
use pe::{read_file_version, read_pe_version, read_product_version};
@@ -113,105 +110,16 @@ pub(super) struct Version {
impl Version {
pub(super) fn read_file_version(file_path: &Path) -> Result<Option<Self>, Error> {
let version = read_pe_version(file_path, read_file_version)?;
let pelite_version = Self::read_version(file_path, |v| {
v.fixed().map(|f| {
format!(
"{}.{}.{}.{}",
f.dwFileVersion.Major,
f.dwFileVersion.Minor,
f.dwFileVersion.Patch,
f.dwFileVersion.Build
)
})
});
compare_versions(file_path, version, pelite_version)
read_pe_version(file_path, read_file_version)
}
pub(super) fn read_product_version(file_path: &Path) -> Result<Option<Self>, Error> {
let version = read_pe_version(file_path, read_product_version)?;
let pelite_version = Self::read_version(file_path, |v| {
v.translation()
.first()
.and_then(|language| v.value(*language, "ProductVersion"))
});
compare_versions(file_path, version, pelite_version)
read_pe_version(file_path, read_product_version)
}
pub(super) fn is_readable(file_path: &Path) -> bool {
read_pe_version(file_path, |_| Ok(None)).is_ok()
}
fn read_version<F: Fn(VersionInfo) -> Option<String>>(
file_path: &Path,
formatter: F,
) -> Result<Option<Self>, Error> {
#[cfg(any(windows, unix))]
let result = {
let file_map = pelite::FileMap::open(file_path)
.map_err(|e| Error::IoError(file_path.to_path_buf(), e))?;
get_pe_version_info(file_map.as_ref()).map(formatter)
};
#[cfg(not(any(windows, unix)))]
let result = {
let bytes =
std::fs::read(file_path).map_err(|e| Error::IoError(file_path.to_path_buf(), e))?;
get_pe_version_info(&bytes).map(formatter)
};
match result {
Ok(s) => Ok(s.map(Version::from)),
Err(FindError::NotFound) => Ok(None),
Err(e) => Err(Error::PeParsingError(file_path.to_path_buf(), Box::new(e))),
}
}
}
fn compare_versions(
file_path: &Path,
version: Option<Version>,
pelite_version: Result<Option<Version>, Error>,
) -> Result<Option<Version>, Error> {
match pelite_version {
Ok(pv) if pv == version => Ok(version),
Ok(pv) => Err(Error::PeParsingError(
file_path.to_path_buf(),
format!("Versions parsed by object and pelite don't match: {version:?} != {pv:?}")
.into(),
)),
Err(e) => Err(Error::PeParsingError(
file_path.to_path_buf(),
format!("pelite errored when parsing version but built-in parser did not: {e}").into(),
)),
}
}
fn get_pe_version_info(bytes: &[u8]) -> Result<VersionInfo<'_>, FindError> {
get_pe_resources(bytes)?.version_info()
}
fn get_pe_resources(bytes: &[u8]) -> Result<Resources<'_>, pelite::Error> {
use pelite::pe64;
match pe64::PeFile::from_bytes(bytes) {
Ok(file) => {
use pelite::pe64::Pe;
file.resources()
}
Err(pelite::Error::PeMagic) => {
use pelite::pe32::{Pe, PeFile};
PeFile::from_bytes(bytes)?.resources()
}
Err(e) => Err(e),
}
}
fn is_separator(c: char) -> bool {