From 40b9ec9b01d251d687a7550aae291aea2bfece46 Mon Sep 17 00:00:00 2001 From: Novattz Date: Sat, 17 Jan 2026 17:57:17 +0100 Subject: [PATCH] bitness detection --- src-tauri/src/main.rs | 1 + src-tauri/src/utils/bitness.rs | 158 +++++++++++++++++++++++++++++++++ src-tauri/src/utils/mod.rs | 1 + 3 files changed, 160 insertions(+) create mode 100644 src-tauri/src/utils/bitness.rs create mode 100644 src-tauri/src/utils/mod.rs diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 01a990f..c3ef2e7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,6 +4,7 @@ )] mod cache; +mod utils; mod dlc_manager; mod installer; mod searcher; diff --git a/src-tauri/src/utils/bitness.rs b/src-tauri/src/utils/bitness.rs new file mode 100644 index 0000000..fbaaadf --- /dev/null +++ b/src-tauri/src/utils/bitness.rs @@ -0,0 +1,158 @@ +use log::{debug, info, warn}; +use std::fs; +use std::path::Path; +use walkdir::WalkDir; + +/// Represents the bitness of a binary +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Bitness { + Bit32, + Bit64, +} + +/// Detect the bitness of a Linux Binary by reading ELF header +/// ELF format: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format +fn detect_binary_bitness(file_path: &Path) -> Option { + // Read first 5 bytes of the file to check ELF header + let bytes = match fs::read(file_path) { + Ok(b) if b.len() >= 5 => b, + _ => return None, + }; + + // Check for ELF magic number (0x7F 'E' 'L' 'F') + if bytes.len() < 5 || &bytes[0..4] != b"\x7ELF" { + return None; + } + + // Byte 4 (EI_CLASS) indicates 32-bit or 64-bit + // 1 = ELFCLASS32 (32-bit) + // 2 = ELFCLASS64 (64-bit) + match bytes[4] { + 1 => Some(Bitness::Bit32), + 2 => Some(Bitness::Bit64), + _ => None, + } +} + +/// Scan game directory for Linux binaries and determine bitness +/// Returns the detected bitness, prioritizing the main game executable +pub fn detect_game_bitness(game_path: &str) -> Result { + info!("Detecting bitness for game at: {}", game_path); + + let game_path_obj = Path::new(game_path); + if !game_path_obj.exists() { + return Err("Game path does not exist".to_string()); + } + + // Directories to skip for performance + let skip_dirs = [ + "videos", + "video", + "movies", + "movie", + "sound", + "sounds", + "audio", + "textures", + "music", + "localization", + "shaders", + "logs", + "assets", + "_CommonRedist", + ]; + + // Limit scan depth to avoid deep recursion + const MAX_DEPTH: usize = 5; + + let mut bit64_binaries = Vec::new(); + let mut bit32_binaries = Vec::new(); + + // Scan for Linux binaries + for entry in WalkDir::new(game_path_obj) + .max_depth(MAX_DEPTH) + .follow_links(false) + .into_iter() + .filter_entry(|e| { + if e.file_type().is_dir() { + let dir_name = e.file_name().to_string_lossy().to_lowercase(); + !skip_dirs.iter().any(|&skip| dir_name.contains(skip)) + } else { + true + } + }) + .filter_map(Result::ok) + { + let path = entry.path(); + + // Only check files + if !path.is_file() { + continue; + } + + // Check if file is executable + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Ok(metadata) = fs::metadata(path) { + let permissions = metadata.permissions(); + // Check if executable bit is set + if permissions.mode() & 0o111 == 0 { + continue; + } + } else { + continue; + } + } + + // Check for common Linux executable extensions or no extension + let filename = path.file_name().unwrap_or_default().to_string_lossy(); + let has_exe_extension = filename.ends_with(".x86") + || filename.ends_with(".x86_64") + || filename.ends_with(".bin") + || filename.contains('.'); + + // Also check for .so files (shared libraries) as they indicate bitness + let is_shared_lib = filename.ends_with(".so") + || filename.contains(".so") + || filename.starts_with("lib"); + + if !has_exe_extension && !is_shared_lib { + continue; + } + + // Detect bitness + if let Some(bitness) = detect_binary_bitness(path) { + debug!("Found {:?} binary: {}", bitness, path.display()); + + match bitness { + Bitness::Bit64 => bit64_binaries.push(path.to_path_buf()), + Bitness::Bit32 => bit32_binaries.push(path.to_path_buf()), + } + } + } + + // Decision logic: prioritize finding the main game executable + // 1. If we found any 64-bit binaries and no 32-bit, it's 64-bit + // 2. If we found any 32-bit binaries and no 64-bit, it's 32-bit + // 3. If we found both, prefer 64-bit (modern games are usually 64-bit) + // 4. If we found neither, return an error + + if !bit64_binaries.is_empty() && bit32_binaries.is_empty() { + info!("Detected 64-bit game (Only 64-bit binaries found"); + Ok(Bitness::Bit64) + } else if !bit32_binaries.is_empty() && bit64_binaries.is_empty() { + info!("Detected 32-bit game (Only 32-bit binaries found"); + Ok(Bitness::Bit32) + } else if !bit64_binaries.is_empty() && !bit32_binaries.is_empty() { + warn!( + "Found both 32-bit and 64-bit binaries, defaulting to 64-bit. 32-bit: {}, 64-bit: {}", + bit32_binaries.len(), + bit64_binaries.len() + ); + info!("Detected 64-bit game (mixed binaries, defaulting to 64-bit)"); + Ok(Bitness::Bit64) + } else { + Err("Could not detect game bitness: no Linux binaries found".to_string()) + } +} \ No newline at end of file diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs new file mode 100644 index 0000000..cd1f5a7 --- /dev/null +++ b/src-tauri/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod bitness; \ No newline at end of file