diff --git a/bin/src/modules/binarize/error/bbw1_tools_not_found.rs b/bin/src/modules/binarize/error/bbw1_tools_not_found.rs index 131de792..bb67d7c4 100644 --- a/bin/src/modules/binarize/error/bbw1_tools_not_found.rs +++ b/bin/src/modules/binarize/error/bbw1_tools_not_found.rs @@ -16,13 +16,19 @@ impl Code for ToolsNotFound { } fn message(&self) -> String { - String::from("Arma 3 Tools not found in registry.") + String::from("Arma 3 Tools not found.") } fn help(&self) -> Option { - Some(String::from( - "Install Arma 3 Tools from Steam and run them at least once.", - )) + if cfg!(windows) { + Some(String::from( + "Install Arma 3 Tools from Steam and run them at least once.", + )) + } else { + Some(String::from( + "Install Arma 3 Tools, and ensure either `wine64` or Proton Sniper is installed.", + )) + } } fn diagnostic(&self) -> Option { diff --git a/bin/src/modules/binarize/mod.rs b/bin/src/modules/binarize/mod.rs index 9d583ba2..f4b2863d 100644 --- a/bin/src/modules/binarize/mod.rs +++ b/bin/src/modules/binarize/mod.rs @@ -86,6 +86,7 @@ impl Module for Binarize { #[cfg(not(windows))] fn init(&mut self, ctx: &Context) -> Result { + use dirs::home_dir; use hemtt_common::steam; let mut report = Report::new(); @@ -117,7 +118,17 @@ impl Module for Binarize { let mut cmd = Command::new("wine64"); cmd.arg("--version"); if cmd.output().is_err() { - self.proton = true; + if home_dir() + .expect("home directory exists") + .join(".local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/run") + .exists() + { + self.proton = true; + } else { + debug!("tools found, but not wine64 or proton"); + report.push(ToolsNotFound::code(Severity::Warning)); + self.command = None; + } } } else { report.push(ToolsNotFound::code(Severity::Warning)); diff --git a/libs/p3d/src/functions/missing.rs b/libs/p3d/src/functions/missing.rs index 8567d902..7a58fd16 100644 --- a/libs/p3d/src/functions/missing.rs +++ b/libs/p3d/src/functions/missing.rs @@ -68,7 +68,7 @@ impl P3D { } else { format!("\\{texture}") }; - let texture = texture.to_lowercase(); + // let texture = texture.to_lowercase(); if let Some(exists) = cache.exists(&texture) { if !exists { missing_textures.push(texture); diff --git a/libs/workspace/src/path.rs b/libs/workspace/src/path.rs index 79e4979b..06712e71 100644 --- a/libs/workspace/src/path.rs +++ b/libs/workspace/src/path.rs @@ -1,4 +1,8 @@ -use std::{hash::Hasher, sync::Arc}; +use std::{ + hash::Hasher, + path::{Path, PathBuf}, + sync::Arc, +}; use hemtt_common::strip::StripInsensitive; use vfs::{SeekAndWrite, VfsPath}; @@ -184,6 +188,7 @@ impl WorkspacePath { }) } + #[allow(clippy::too_many_lines)] /// Locate a path in the workspace /// /// Checks in order: @@ -194,6 +199,9 @@ impl WorkspacePath { /// /// # Errors /// [`Error::Vfs`] if the path could not be located + /// + /// # Panics + /// If the found path is not valid utf-8 pub fn locate(&self, path: &str) -> Result, Error> { fn is_wrong_case(on_disk: &VfsPath, requested: &str) -> bool { let on_disk = on_disk.as_str().replace('\\', "/"); @@ -244,25 +252,13 @@ impl WorkspacePath { .iter() .find(|(p, _)| path_lower.starts_with(&format!("{}/", p.to_lowercase()))) { - // Windows needs case insensitivity because p3ds are a - // disaster. On Linux we'll be more strict to avoid - // pain and suffering. - let ret_path = if cfg!(windows) { - root.join( - path_lower - .strip_prefix(&base.to_lowercase()) - .unwrap_or(&path) - .strip_prefix('/') - .unwrap_or(&path), - )? - } else { - root.join( - path.strip_prefix_insensitive(base) - .unwrap_or(&path) - .strip_prefix('/') - .unwrap_or(&path), - )? - }; + let ret_path = root.join( + path.strip_prefix_insensitive(base) + .unwrap_or(&path) + .strip_prefix('/') + .unwrap_or(&path), + )?; + let path_str = ret_path.as_str().trim_start_matches('/'); if ret_path.exists()? { return Ok(Some(LocateResult { case_mismatch: if is_wrong_case(&ret_path, &path) { @@ -277,7 +273,35 @@ impl WorkspacePath { }), }, })); + } else if let Some(insensitive) = exists_case_insensitive(Path::new(path_str)) { + return Ok(Some(LocateResult { + case_mismatch: Some(insensitive.to_string_lossy().to_string()), + path: Self { + data: Arc::new(WorkspacePathData { + path: self + .data + .workspace + .vfs + .join(insensitive.to_str().expect("utf-8"))?, + workspace: self.data.workspace.clone(), + }), + }, + })); } + } else if let Some(insensitive) = exists_case_insensitive(Path::new(&path)) { + return Ok(Some(LocateResult { + case_mismatch: Some(insensitive.to_string_lossy().to_string()), + path: Self { + data: Arc::new(WorkspacePathData { + path: self + .data + .workspace + .vfs + .join(insensitive.to_str().expect("utf-8"))?, + workspace: self.data.workspace.clone(), + }), + }, + })); } } let ret_path = self.data.path.parent().join(&path)?; @@ -393,3 +417,36 @@ pub struct LocateResult { pub path: WorkspacePath, pub case_mismatch: Option, } + +/// Check if a path exists in a case-insensitive manner. +fn exists_case_insensitive(path: &Path) -> Option { + exists_case_insensitive_from(path, PathBuf::from(".")) + .or_else(|| exists_case_insensitive_from(path, PathBuf::from("./include"))) +} + +fn exists_case_insensitive_from(path: &Path, from: PathBuf) -> Option { + let mut current_path = from; + for component in path.components() { + if let std::path::Component::Normal(name) = component { + let mut found = false; + if let Ok(entries) = std::fs::read_dir(¤t_path) { + for entry in entries.filter_map(Result::ok) { + if entry.file_name().eq_ignore_ascii_case(name) { + current_path.push(entry.file_name()); + found = true; + break; + } + } + } + if !found { + return None; + } + } + } + + if current_path.exists() { + Some(current_path) + } else { + None + } +}