use pelite::pe64;
use pelite::resources::{FindError, Resources, version_info::VersionInfo};
use rayon::prelude::*;
use std::cmp::Ordering;
use std::fs::{canonicalize, read_dir, File};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::{RLibError, Result};
pub fn parse_str_as_bool(string: &str) -> Result<bool> {
let str_lower_case = string.to_lowercase();
if str_lower_case == "true" || str_lower_case == "1" {
Ok(true)
}
else if str_lower_case == "false" || str_lower_case == "0" {
Ok(false)
}
else {
Err(RLibError::ParseBoolError(string.to_owned()))
}
}
pub fn starts_with_case_insensitive(full_str: &str, partial_str: &str) -> bool {
let full_str_chars = full_str.chars().count();
let partial_str_chars = partial_str.chars().count();
if full_str_chars > partial_str_chars {
let partial_str_len_in_bytes = partial_str.len();
let full_str_max_index = full_str.char_indices().map(|(index, _)| index).find(|index| index >= &partial_str_len_in_bytes).unwrap_or(full_str.len());
let full_str_base = &full_str[..full_str_max_index];
caseless::default_caseless_match_str(full_str_base, partial_str)
} else {
false
}
}
pub fn closest_valid_char_byte(string: &str, start_byte: usize) -> usize {
if start_byte < string.len() && string.get(start_byte..).is_some() { start_byte }
else if start_byte + 1 < string.len() && string.get(start_byte + 1..).is_some() { start_byte + 1 }
else if start_byte + 2 < string.len() && string.get(start_byte + 2..).is_some() { start_byte + 2 }
else if start_byte + 3 < string.len() && string.get(start_byte + 3..).is_some() { start_byte + 3 }
else { unimplemented!() }
}
pub fn line_column_from_string_pos(string: &str, pos: u64) -> (u64, u64) {
let mut row = 0;
let mut col = 0;
let mut pos_processed = 0;
let end_skip = if string.contains("\r\n") { 2 } else { 1 };
for (index, line) in string.lines().enumerate() {
if pos > pos_processed + line.len() as u64 {
pos_processed += line.len() as u64 + end_skip;
continue;
}
else {
row = index as u64;
col = pos.checked_sub(pos_processed).unwrap_or_default();
break;
}
}
(row, col)
}
pub fn files_from_subdir(current_path: &Path, scan_subdirs: bool) -> Result<Vec<PathBuf>> {
if !scan_subdirs {
return Ok(read_dir(current_path)?
.flatten()
.filter(|file| {
if let Ok(metadata) = file.metadata() {
metadata.is_file()
} else { false }
})
.map(|file| file.path()).collect());
}
let mut file_list: Vec<PathBuf> = vec![];
match read_dir(current_path) {
Ok(files_in_current_path) => {
for file in files_in_current_path {
match file {
Ok(file) => {
let file_path = file.path();
if file_path.is_file() {
file_list.push(file_path);
}
else if file_path.is_dir() && scan_subdirs {
let mut subfolder_files_path = files_from_subdir(&file_path, scan_subdirs)?;
file_list.append(&mut subfolder_files_path);
}
}
Err(_) => return Err(RLibError::ReadFileFolderError(current_path.to_string_lossy().to_string())),
}
}
}
Err(_) => return Err(RLibError::ReadFileFolderError(current_path.to_string_lossy().to_string())),
}
Ok(file_list)
}
pub fn final_folders_from_subdir(current_path: &Path, ignore_empty_folders: bool) -> Result<Vec<PathBuf>> {
let mut folder_list: Vec<PathBuf> = vec![];
match read_dir(current_path) {
Ok(dir_entry_in_current_path) => {
let mut has_subfolders = false;
let mut has_files = false;
for dir_entry in dir_entry_in_current_path {
match dir_entry {
Ok(dir_entry) => {
let path = dir_entry.path();
if path.is_file() {
has_files = true;
continue;
}
if path.is_dir() {
let mut subfolder_files_path = final_folders_from_subdir(&path, ignore_empty_folders)?;
folder_list.append(&mut subfolder_files_path);
has_subfolders = true;
}
}
Err(_) => return Err(RLibError::ReadFileFolderError(current_path.to_string_lossy().to_string())),
}
}
if !has_subfolders && (!ignore_empty_folders || has_files) {
folder_list.push(current_path.to_path_buf());
}
}
Err(_) => return Err(RLibError::ReadFileFolderError(current_path.to_string_lossy().to_string())),
}
Ok(folder_list)
}
pub fn oldest_file_in_folder(current_path: &Path) -> Result<Option<PathBuf>> {
let files = files_in_folder_from_newest_to_oldest(current_path)?;
Ok(files.last().cloned())
}
pub fn files_in_folder_from_newest_to_oldest(current_path: &Path) -> Result<Vec<PathBuf>> {
let mut files = files_from_subdir(current_path, false)?;
files.sort();
files.sort_by(|a, b| {
if let Ok(a) = File::open(a) {
if let Ok(b) = File::open(b) {
if let Ok(a) = last_modified_time_from_file(&a) {
if let Ok(b) = last_modified_time_from_file(&b) {
b.cmp(&a)
} else { Ordering::Equal}
} else { Ordering::Equal}
} else { Ordering::Equal}
} else { Ordering::Equal}
});
Ok(files)
}
pub fn path_to_absolute_string(path: &Path) -> String {
let mut path_str = path.to_string_lossy().to_string();
match canonicalize(path) {
Ok(cannon_path) => {
let cannon_path_str = cannon_path.to_string_lossy();
if let Some(strip) = cannon_path_str.strip_prefix("\\\\?\\") {
path_str = strip.to_owned();
} else {
path_str = cannon_path_str.to_string();
}
},
Err(_) => {
if path_str.starts_with("\\\\?\\") {
path_str = path_str[4..].to_owned();
}
}
}
path_str
}
pub fn path_to_absolute_path(path: &Path, strip_prefix: bool) -> PathBuf {
let mut path = path.to_owned();
match canonicalize(&path) {
Ok(cannon_path) => {
let cannon_path_str = cannon_path.to_string_lossy();
if strip_prefix {
if let Some(strip) = cannon_path_str.strip_prefix("\\\\?\\") {
path = PathBuf::from(strip);
} else {
path = cannon_path;
}
} else {
path = cannon_path;
}
},
Err(_) => {
let path_str = path.to_string_lossy();
if strip_prefix {
if let Some(strip) = path_str.strip_prefix("\\\\?\\") {
path = PathBuf::from(strip);
}
}
}
}
path
}
pub fn current_time() -> Result<u64> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs())
}
pub fn last_modified_time_from_file(file: &File) -> Result<u64> {
Ok(file.metadata()?.modified()?.duration_since(UNIX_EPOCH)?.as_secs())
}
pub fn last_modified_time_from_files(paths: &[PathBuf]) -> Result<u64> {
Ok(paths
.par_iter()
.filter_map(|path| File::open(path).ok())
.filter_map(|file| last_modified_time_from_file(&file).ok())
.max().unwrap_or(0)
)
}
pub(crate) fn pe_version_info(bytes: &'_ [u8]) -> std::result::Result<VersionInfo<'_>, FindError> {
pe_resources(bytes)?.version_info()
}
pub(crate) fn pe_resources(bytes: &'_ [u8]) -> std::result::Result<Resources<'_>, pelite::Error> {
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),
}
}
const VWISE_HASH_VALUE: u32 = 0x811C9DC5;
const VWISE_MULT_VALUE: u32 = 0x01000193;
const VWISE_AND_VALUE: u32 = 0xFFFFFFFF;
pub fn hash_vwise(name: &str) -> u32 {
let name = name.trim().to_lowercase();
let mut hash_value = VWISE_HASH_VALUE;
for byte in name.as_bytes() {
hash_value *= VWISE_MULT_VALUE;
hash_value ^= *byte as u32;
hash_value &= VWISE_AND_VALUE;
}
hash_value
}
pub const INVALID_CHARACTERS_WINDOWS: [char; 9] = [
'<',
'>',
':',
'"',
'/',
'\\',
'|',
'?',
'*',
];
pub const DEFAULT_FILENAME: &str = "unnamed_file";
pub fn sanitize_path(path: &Path) -> PathBuf {
if let Some(file_name) = path.file_name() {
let sanitized_name = sanitize_filename(file_name.to_string_lossy().as_ref());
let mut sanitized_path = path.to_path_buf();
sanitized_path.set_file_name(sanitized_name);
sanitized_path
} else {
path.to_path_buf()
}
}
pub fn sanitize_filename(filename: &str) -> String {
let mut sanitized = filename.to_string();
for &ch in &INVALID_CHARACTERS_WINDOWS {
sanitized = sanitized.replace(ch, "_");
}
sanitized = sanitized.trim().trim_matches('.').to_string();
if sanitized.is_empty() {
sanitized = DEFAULT_FILENAME.to_string();
}
sanitized
}
pub(crate) fn check_size_mismatch(curr_pos: usize, expected_pos: usize) -> Result<()> {
if curr_pos != expected_pos {
return Err(RLibError::DecodingMismatchSizeError(expected_pos, curr_pos));
}
Ok(())
}