├── assets ├── icon.ico └── icon.png ├── example_img ├── bmp1.bmp ├── gif1.gif ├── jpg1.jpg ├── pcx1.pcx ├── png1.png └── tga1.tga ├── src ├── esp_partition.rs ├── winfunc.rs ├── platform │ ├── mod.rs │ ├── windows.rs │ └── linux.rs ├── i18n.rs ├── lenlogo.rs └── main.rs ├── .gitignore ├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── Cargo.toml ├── LICENSE └── README.md /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/assets/icon.png -------------------------------------------------------------------------------- /example_img/bmp1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/bmp1.bmp -------------------------------------------------------------------------------- /example_img/gif1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/gif1.gif -------------------------------------------------------------------------------- /example_img/jpg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/jpg1.jpg -------------------------------------------------------------------------------- /example_img/pcx1.pcx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/pcx1.pcx -------------------------------------------------------------------------------- /example_img/png1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/png1.png -------------------------------------------------------------------------------- /example_img/tga1.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chnzzh/lenovo-logo-changer/HEAD/example_img/tga1.tga -------------------------------------------------------------------------------- /src/esp_partition.rs: -------------------------------------------------------------------------------- 1 | // ESP分区操作模块 2 | // 使用平台抽象层来实现跨平台兼容 3 | 4 | use crate::platform::{EspPartitionOps, NativePlatform}; 5 | 6 | /// 删除ESP分区中的Logo路径 7 | pub(crate) fn delete_logo_path() -> bool { 8 | NativePlatform::delete_logo_path() 9 | } 10 | 11 | /// 复制文件到ESP分区 12 | pub(crate) fn copy_file_to_esp(src: &str, dst: &str) -> bool { 13 | NativePlatform::copy_file_to_esp(src, dst) 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /src/winfunc.rs: -------------------------------------------------------------------------------- 1 | use windows_sys::{ 2 | Win32::System::Threading::*,Win32::Security::*, 3 | }; 4 | 5 | pub(crate) fn is_admin() -> bool { 6 | unsafe { 7 | let mut is_admin = 0u32; 8 | let mut size = std::mem::size_of::() as u32; 9 | let mut token = CreateEventW(std::ptr::null(), 1, 0, std::ptr::null()); 10 | OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &mut token); 11 | GetTokenInformation( 12 | token, 13 | TokenElevation, 14 | &mut is_admin as *mut u32 as *mut std::ffi::c_void, 15 | size, 16 | &mut size, 17 | ); 18 | log::debug!("is_admin: {}", is_admin); 19 | if is_admin == 0 { 20 | false 21 | } 22 | else { 23 | true 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | name: release ${{ matrix.target }} 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: x86_64-pc-windows-gnu 16 | archive_type: zip 17 | archive_name: lenovo_logo_changer_win_x86_64 18 | steps: 19 | - uses: actions/checkout@master 20 | - name: Compile and release 21 | uses: rust-build/rust-build.action@v1.4.5 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | RUSTTARGET: ${{ matrix.target }} 26 | EXTRA_FILES: "README.md LICENSE" 27 | ARCHIVE_TYPES: ${{ matrix.archive_type }} 28 | ARCHIVE_NAME: ${{ matrix.archive_name }} 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lenovo-logo-changer" 3 | version = "0.2.0" 4 | edition = "2024" 5 | build = "build.rs" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | env_logger = "0.11" 11 | log = "0.4" 12 | efivar = "2.0.0" 13 | eframe = "0.33.0" 14 | egui = "0.33.0" 15 | hex = "0.4.3" 16 | rfd = "0.15.4" 17 | sha2 = "0.10.9" 18 | poll-promise = "0.3" 19 | 20 | # Windows特定依赖 21 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys] 22 | version = "0.61.2" 23 | features = ["Win32_Foundation", "Win32_Security", "Win32_System_Threading", "Win32_Storage_FileSystem"] 24 | 25 | # Linux特定依赖 26 | [target.'cfg(target_os = "linux")'.dependencies] 27 | libc = "0.2" 28 | 29 | [build-dependencies] 30 | winresource = "0.1" 31 | 32 | [profile.release] 33 | strip = true 34 | lto = true 35 | opt-level = "z" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 chnzzh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Install target 20 | run: rustup target add x86_64-pc-windows-gnu 21 | - name: Install mingw-w64 22 | run: sudo apt install mingw-w64 -y 23 | - name: Build for Windows GNU 24 | run: cargo build --verbose --target x86_64-pc-windows-gnu --release 25 | - name: Build for ubuntu 26 | run: cargo build --verbose --release 27 | - name: Upload Windows GNU binary 28 | uses: actions/upload-artifact@v4 29 | with: 30 | name: lenovo-logo-changer-win-x86_64-${{ matrix.runs-on }} 31 | path: target/x86_64-pc-windows-gnu/release/lenovo-logo-changer.exe 32 | - name: Upload Ubuntu binary 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: lenovo-logo-changer-linux-x86_64-${{ matrix.runs-on }} 36 | path: target/release/lenovo-logo-changer 37 | -------------------------------------------------------------------------------- /src/platform/mod.rs: -------------------------------------------------------------------------------- 1 | // 平台抽象层模块 2 | 3 | // Windows平台支持(包括在Linux上交叉编译Windows目标) 4 | #[cfg(any(target_os = "windows", all(target_family = "windows")))] 5 | mod windows; 6 | 7 | #[cfg(any(target_os = "windows", all(target_family = "windows")))] 8 | pub use windows::WindowsPlatform as NativePlatform; 9 | 10 | // Linux平台支持 11 | #[cfg(target_os = "linux")] 12 | pub mod linux; 13 | 14 | #[cfg(target_os = "linux")] 15 | pub use linux::LinuxPlatform as NativePlatform; 16 | 17 | // 为未实现的平台提供编译时错误提示 18 | #[cfg(not(any(target_os = "windows", all(target_family = "windows"), target_os = "linux")))] 19 | compile_error!("This platform is not yet supported. Currently only Windows and Linux are supported. Please add support for your platform in src/platform/"); 20 | 21 | /// 平台操作trait,定义所有平台特定的操作接口 22 | pub trait PlatformOps { 23 | /// 检查是否具有管理员/root权限 24 | fn is_admin() -> bool; 25 | 26 | /// 查找可用的驱动器盘符(Windows特有,其他平台可能不需要) 27 | fn find_available_drive() -> Option; 28 | 29 | /// 挂载ESP分区到指定路径 30 | /// 31 | /// # 参数 32 | /// * `mount_point` - 挂载点(Windows下是盘符,Linux下是路径) 33 | /// 34 | /// # 返回值 35 | /// 成功返回true,失败返回false 36 | fn mount_esp(mount_point: &str) -> bool; 37 | 38 | /// 卸载ESP分区 39 | /// 40 | /// # 参数 41 | /// * `mount_point` - 挂载点 42 | fn unmount_esp(mount_point: &str) -> bool; 43 | 44 | /// 获取Boot加载图标状态 45 | /// 46 | /// # 返回值 47 | /// true表示显示加载图标,false表示隐藏 48 | fn get_loading_icon() -> bool; 49 | 50 | /// 设置Boot加载图标状态 51 | /// 52 | /// # 参数 53 | /// * `show_loading_icon` - true表示显示加载图标,false表示隐藏 54 | /// 55 | /// # 返回值 56 | /// 成功返回true,失败返回false 57 | fn set_loading_icon(show_loading_icon: bool) -> bool; 58 | 59 | /// 获取系统字体路径(用于UI显示) 60 | fn get_system_font_path() -> Option; 61 | } 62 | 63 | /// ESP分区操作trait 64 | pub trait EspPartitionOps { 65 | /// 复制文件到ESP分区 66 | /// 67 | /// # 参数 68 | /// * `src` - 源文件路径 69 | /// * `dst` - 目标路径(相对于ESP分区根目录) 70 | /// 71 | /// # 返回值 72 | /// 成功返回true,失败返回false 73 | fn copy_file_to_esp(src: &str, dst: &str) -> bool; 74 | 75 | /// 删除ESP分区中的Logo路径 76 | /// 77 | /// # 返回值 78 | /// 成功返回true,失败返回false 79 | fn delete_logo_path() -> bool; 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lenovo UEFI Boot Logo Changer 2 | 3 | ![GitHub License](https://img.shields.io/github/license/chnzzh/lenovo-logo-changer) 4 | ![GitHub top language](https://img.shields.io/github/languages/top/chnzzh/lenovo-logo-changer) 5 | ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/chnzzh/lenovo-logo-changer/release.yml) 6 | ![Static Badge](https://img.shields.io/badge/!!!SeeReadmeFirst!!!-orangered) 7 | 8 | *Lenovo UEFI Boot Logo Changer* is a rust program designed to modify the Boot startup logo on Lenovo devices with UEFI firmware. 9 | This tool allows you to customize the boot logo with different format image. 10 | 11 | ![20240128171115](https://github.com/chnzzh/lenovo-logo-changer/assets/41407837/674d7db6-e2af-4360-956d-edacf9fe5157) 12 | 13 | **[Download](https://github.com/chnzzh/lenovo-logo-changer/releases/latest) the latest executable file compiled by GitHub Actions** 14 | 15 | You can also refer to [How to build](#how-to-build) to compile it yourself. 16 | 17 | ## Important 18 | 19 | + **This program involves modifications to UEFI variables and the ESP partition. Please ensure to backup important files before usage.** 20 | + **This program will not check if the image files you are using comply with the correct image format. Please ensure that your images can function properly.** (Otherwise your system may be compromised: [LogoFAIL](https://binarly.io/posts/finding_logofail_the_dangers_of_image_parsing_during_system_boot/)) 21 | + This program is intended for personal research use only. 22 | + **All risks are assumed by the user**. 23 | 24 | ## Usage 25 | 26 | ![ui](https://github.com/user-attachments/assets/b1d5112e-3bcb-44c2-8c9b-d43669285cfd) 27 | 28 | ### Windows 29 | 30 | + Right-click on the executable file and run it in administrator mode. 31 | + Click "Open Image" to upload a suitable image. 32 | + Click "Change Logo" 33 | 34 | 35 | 36 | ### Linux 37 | 38 | + Run the program with root privileges: 39 | ```bash 40 | sudo ./lenovo-logo-changer 41 | ``` 42 | + Click "Open Image" to upload a suitable image. 43 | + Click "Change Logo" 44 | 45 | ## How it Works 46 | 47 | Lenovo UEFI Boot Logo Changer operates by leveraging Lenovo's support for user customization of the boot logo through the ESP (EFI System Partition). 48 | The process involves placing a custom image into the ESP partition and then configuring UEFI variables to instruct the DXE (Driver Execution Environment) program to read and display the user-defined logo during the system's boot process. 49 | 50 | So this tool do: 51 | 52 | 1. **Read UEFI Variables** to determine whether the system supports Logo Change; 53 | 2. **Place Selected Image in ESP Partition**; 54 | 3. **Modify UEFI Variables** to enable the UEFI program to correctly set and display the customized logo. 55 | 56 | All of the above operations need to be performed with administrator privileges. 57 | 58 | ## How to build 59 | 60 | ### On Linux 61 | 62 | 1. Install Rust and MinGW: 63 | ```bash 64 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 65 | sudo apt install mingw-w64 -y 66 | ``` 67 | 68 | #### For Linux 69 | 70 | 2. Build the project: 71 | ```bash 72 | cargo build --release 73 | ``` 74 | 75 | #### For Windows 76 | 77 | 2. Add the Windows target for Rust: 78 | ```bash 79 | rustup target add x86_64-pc-windows-gnu 80 | ``` 81 | 82 | 3. Build the project: 83 | ```bash 84 | cargo build --release --target x86_64-pc-windows-gnu 85 | ``` 86 | 87 | ## Supported Types 88 | 89 | + ThinkBook 14 G4+ ARA 90 | + ThinkBook 16 G5+ ARP 91 | + IdeaPad Slim 5 14AHP9 (83DB) 92 | + Lenovo LOQ 15IRH8 93 | + Lenovo Yoga Slim 7 Aura Edition 15,3" 94 | + Lenovo LOQ 15ARP9 95 | + ... 96 | -------------------------------------------------------------------------------- /src/i18n.rs: -------------------------------------------------------------------------------- 1 | // Simple i18n module for UI strings 2 | 3 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 4 | pub enum Lang { 5 | En, 6 | Zh, 7 | } 8 | 9 | impl Lang { 10 | pub fn from_code(code: &str) -> Self { 11 | match code { 12 | "zh" | "zh-CN" | "zh_CN" => Lang::Zh, 13 | _ => Lang::En, 14 | } 15 | } 16 | } 17 | 18 | use std::borrow::Cow; 19 | 20 | pub fn t(lang: Lang, key: &str) -> Cow<'static, str> { 21 | match lang { 22 | Lang::En => match key { 23 | // General 24 | "language" => Cow::Borrowed("Language"), 25 | "english" => Cow::Borrowed("English"), 26 | "chinese" => Cow::Borrowed("Chinese"), 27 | // Support status 28 | "supported" => Cow::Borrowed("Your device is supported !"), 29 | "unsupported" => Cow::Borrowed("Your device is not supported !"), 30 | // UEFI logo state 31 | "logo_enabled" => Cow::Borrowed("UEFI Logo DIY Enabled"), 32 | "logo_disabled" => Cow::Borrowed("UEFI Logo DIY Disabled"), 33 | // Info labels 34 | "max_image_size" => Cow::Borrowed("Max Image Size"), 35 | "supported_formats" => Cow::Borrowed("Support Format"), 36 | "version" => Cow::Borrowed("Version"), 37 | // Actions 38 | "show_windows_loading" => Cow::Borrowed("Show Windows loading circle"), 39 | "pick_image" => Cow::Borrowed("Pick Image"), 40 | "picked_image" => Cow::Borrowed("Picked Image:"), 41 | "change_logo_btn" => Cow::Borrowed("!!! Change Logo !!!"), 42 | "restore_logo_btn" => Cow::Borrowed("Restore Logo"), 43 | // Progress 44 | "setting_logo_wait" => Cow::Borrowed("Setting logo, please wait..."), 45 | "restoring_logo_wait" => Cow::Borrowed("Restoring logo, please wait..."), 46 | // Results 47 | "change_logo_success" => Cow::Borrowed("Change logo succeeded, reboot to see the effect"), 48 | "change_logo_failed" => Cow::Borrowed("Change logo failed"), 49 | "restore_logo_success" => Cow::Borrowed("Restore Logo Success"), 50 | "restore_logo_failed" => Cow::Borrowed("Restore Logo Failed"), 51 | // Admin prompt 52 | "admin_required" => Cow::Borrowed("You need to run this program as Administrator !"), 53 | _ => Cow::Owned(key.to_string()), 54 | }, 55 | Lang::Zh => match key { 56 | // General 57 | "language" => Cow::Borrowed("语言"), 58 | "english" => Cow::Borrowed("英语"), 59 | "chinese" => Cow::Borrowed("中文"), 60 | // Support status 61 | "supported" => Cow::Borrowed("您的设备是支持的!"), 62 | "unsupported" => Cow::Borrowed("不支持您的设备!"), 63 | // UEFI logo state 64 | "logo_enabled" => Cow::Borrowed("自定义UEFI Logo已启用"), 65 | "logo_disabled" => Cow::Borrowed("自定义UEFI Logo未启用"), 66 | // Info labels 67 | "max_image_size" => Cow::Borrowed("图片最大分辨率"), 68 | "supported_formats" => Cow::Borrowed("支持的图片格式"), 69 | "version" => Cow::Borrowed("协议版本"), 70 | // Actions 71 | "show_windows_loading" => Cow::Borrowed("显示Windows加载图标"), 72 | "pick_image" => Cow::Borrowed("选择图片"), 73 | "picked_image" => Cow::Borrowed("已选择的图片:"), 74 | "change_logo_btn" => Cow::Borrowed("!!! 设置Logo !!! "), 75 | "restore_logo_btn" => Cow::Borrowed("恢复Logo"), 76 | // Progress 77 | "setting_logo_wait" => Cow::Borrowed("正在设置Logo,请稍候..."), 78 | "restoring_logo_wait" => Cow::Borrowed("正在恢复Logo,请稍候..."), 79 | // Results 80 | "change_logo_success" => Cow::Borrowed("设置Logo成功,重新启动以查看效果"), 81 | "change_logo_failed" => Cow::Borrowed("设置Logo失败"), 82 | "restore_logo_success" => Cow::Borrowed("恢复Logo成功"), 83 | "restore_logo_failed" => Cow::Borrowed("恢复Logo失败"), 84 | // Admin prompt 85 | "admin_required" => Cow::Borrowed("您需要以管理员权限运行此程序!"), 86 | _ => Cow::Owned(key.to_string()), 87 | }, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/platform/windows.rs: -------------------------------------------------------------------------------- 1 | // Windows平台特定实现 2 | 3 | use std::path::Path; 4 | use std::process::Command; 5 | use windows_sys::{ 6 | Win32::System::Threading::*, 7 | Win32::Security::*, 8 | Win32::Storage::FileSystem::GetLogicalDrives, 9 | }; 10 | 11 | use super::{PlatformOps, EspPartitionOps}; 12 | 13 | /// Windows平台实现 14 | pub struct WindowsPlatform; 15 | 16 | impl PlatformOps for WindowsPlatform { 17 | fn is_admin() -> bool { 18 | unsafe { 19 | let mut is_admin = 0u32; 20 | let mut size = std::mem::size_of::() as u32; 21 | let mut token = CreateEventW(std::ptr::null(), 1, 0, std::ptr::null()); 22 | OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &mut token); 23 | GetTokenInformation( 24 | token, 25 | TokenElevation, 26 | &mut is_admin as *mut u32 as *mut std::ffi::c_void, 27 | size, 28 | &mut size, 29 | ); 30 | println!("is_admin: {}", is_admin); 31 | is_admin != 0 32 | } 33 | } 34 | 35 | fn find_available_drive() -> Option { 36 | // 获取逻辑驱动器的位掩码 37 | let drive_mask = unsafe { GetLogicalDrives() }; 38 | 39 | // 从 A 到 Z 检查每个盘符 40 | for drive_letter in b'A'..=b'Z' { 41 | let mask = 1 << (drive_letter - b'A'); 42 | 43 | // 检查位掩码中是否存在当前盘符对应的位 44 | if (drive_mask & mask) == 0 { 45 | // 盘符可用,返回对应的字符 46 | return Some(drive_letter as char); 47 | } 48 | } 49 | // 所有盘符都已用完 50 | None 51 | } 52 | 53 | fn mount_esp(mount_point: &str) -> bool { 54 | // Windows下挂载ESP分区到指定盘符 55 | // mountvol X: /s 56 | use std::os::windows::process::CommandExt; 57 | const CREATE_NO_WINDOW: u32 = 0x08000000; 58 | 59 | let mut mountvol_cmd = Command::new("mountvol"); 60 | mountvol_cmd 61 | .arg(format!("{}:", mount_point)) 62 | .arg("/s") 63 | .creation_flags(CREATE_NO_WINDOW) 64 | .stdout(std::process::Stdio::null()) 65 | .stderr(std::process::Stdio::null()); 66 | 67 | match mountvol_cmd.status() { 68 | Ok(status) => { 69 | if status.success() { 70 | true 71 | } else { 72 | eprintln!("[!] Mountvol failed / 挂载失败"); 73 | false 74 | } 75 | } 76 | Err(e) => { 77 | eprintln!("[!] Mountvol command execution failed / 挂载命令执行失败: {}", e); 78 | false 79 | } 80 | } 81 | } 82 | 83 | fn unmount_esp(mount_point: &str) -> bool { 84 | // Windows下卸载ESP分区 85 | // mountvol X: /d 86 | use std::os::windows::process::CommandExt; 87 | const CREATE_NO_WINDOW: u32 = 0x08000000; 88 | 89 | let mut mountvol_cmd = Command::new("mountvol"); 90 | mountvol_cmd 91 | .arg(format!("{}:", mount_point)) 92 | .arg("/d") 93 | .creation_flags(CREATE_NO_WINDOW) 94 | .stdout(std::process::Stdio::null()) 95 | .stderr(std::process::Stdio::null()); 96 | 97 | match mountvol_cmd.status() { 98 | Ok(status) => { 99 | if status.success() { 100 | true 101 | } else { 102 | eprintln!("[!] Unmountvol failed / 卸载失败"); 103 | false 104 | } 105 | } 106 | Err(e) => { 107 | eprintln!("[!] Unmountvol command execution failed / 卸载命令执行失败: {}", e); 108 | false 109 | } 110 | } 111 | } 112 | 113 | fn get_loading_icon() -> bool { 114 | // 执行 bcdedit /enum all 命令 115 | use std::os::windows::process::CommandExt; 116 | const CREATE_NO_WINDOW: u32 = 0x08000000; 117 | 118 | let mut cmd = Command::new("bcdedit"); 119 | cmd.arg("/enum") 120 | .arg("all") 121 | .creation_flags(CREATE_NO_WINDOW); 122 | 123 | match cmd.output() { 124 | Ok(output) => { 125 | // 将输出转换为字符串 126 | let stdout = String::from_utf8_lossy(&output.stdout); 127 | // 检查输出中是否包含 "bootuxdisabled" 和 "Yes" 128 | for line in stdout.lines() { 129 | if line.contains("bootuxdisabled") && line.contains("Yes") { 130 | println!("Loading icon Disabled"); 131 | return false; // 存在且包含 "Yes" 132 | } 133 | } 134 | println!("Loading icon Enabled"); 135 | true // 不存在或不包含 "Yes" 136 | } 137 | Err(e) => { 138 | eprintln!("Failed to execute command: {}", e); 139 | true // 处理错误,返回 true 140 | } 141 | } 142 | } 143 | 144 | fn set_loading_icon(show_loading_icon: bool) -> bool { 145 | use std::os::windows::process::CommandExt; 146 | const CREATE_NO_WINDOW: u32 = 0x08000000; 147 | 148 | let args = if show_loading_icon { 149 | vec!["-set", "bootuxdisabled", "off"] 150 | } else { 151 | vec!["-set", "bootuxdisabled", "on"] 152 | }; 153 | 154 | let mut cmd = Command::new("bcdedit.exe"); 155 | cmd.args(&args) 156 | .creation_flags(CREATE_NO_WINDOW); 157 | 158 | match cmd.output() { 159 | Ok(output) => { 160 | if output.status.success() { 161 | println!("Command executed successfully"); 162 | true 163 | } else { 164 | eprintln!( 165 | "Command failed: {}", 166 | String::from_utf8_lossy(&output.stderr) 167 | ); 168 | false 169 | } 170 | } 171 | Err(e) => { 172 | eprintln!("Failed to execute command: {}", e); 173 | false 174 | } 175 | } 176 | } 177 | 178 | fn get_system_font_path() -> Option { 179 | // Windows系统字体路径 180 | Some("C:/Windows/Fonts/msyh.ttc".to_string()) 181 | } 182 | } 183 | 184 | impl EspPartitionOps for WindowsPlatform { 185 | fn copy_file_to_esp(src: &str, dst: &str) -> bool { 186 | // 判断src是否为文件 187 | let src_path = Path::new(src); 188 | if !src_path.is_file() { 189 | eprintln!("[!] Source path is not a file / 源路径不是文件"); 190 | return false; 191 | } 192 | 193 | // 获取可用的盘符 194 | let drive_letter = match Self::find_available_drive() { 195 | Some(drive_letter) => drive_letter, 196 | None => { 197 | eprintln!("[!] No available drive letter / 没有可用的盘符"); 198 | return false; 199 | } 200 | }; 201 | println!("drive_letter: {}", drive_letter); 202 | 203 | // 挂载ESP分区 204 | let mount_point = drive_letter.to_string(); 205 | if !Self::mount_esp(&mount_point) { 206 | return false; 207 | } 208 | 209 | let target_path = Path::new(&format!("{}:\\", drive_letter)).join(dst); 210 | 211 | // 如果目标上级路径存在,删除目标路径 212 | if let Some(parent) = target_path.parent() { 213 | if parent.exists() { 214 | if let Err(err) = std::fs::remove_dir_all(parent) { 215 | eprintln!("[!] Remove directory failed / 删除目录失败: {}", err); 216 | Self::unmount_esp(&mount_point); 217 | return false; 218 | } 219 | } 220 | } 221 | 222 | // 创建目标路径 223 | if let Some(parent) = target_path.parent() { 224 | if !parent.exists() { 225 | if let Err(err) = std::fs::create_dir_all(parent) { 226 | eprintln!("[!] Create directory failed / 创建目录失败: {}", err); 227 | Self::unmount_esp(&mount_point); 228 | return false; 229 | } 230 | } 231 | } 232 | 233 | // 将文件复制到目标路径 234 | if let Err(err) = std::fs::copy(src, &target_path) { 235 | eprintln!("[!] Copy file failed / 复制文件失败: {}", err); 236 | Self::unmount_esp(&mount_point); 237 | return false; 238 | } 239 | 240 | println!("[+] File copied successfully / 文件复制成功: {}", target_path.display()); 241 | Self::unmount_esp(&mount_point); 242 | true 243 | } 244 | 245 | fn delete_logo_path() -> bool { 246 | let drive_letter = match Self::find_available_drive() { 247 | Some(drive_letter) => drive_letter, 248 | None => { 249 | eprintln!("[!] No available drive letter / 没有可用的盘符"); 250 | return false; 251 | } 252 | }; 253 | println!("drive_letter: {}", drive_letter); 254 | 255 | let mount_point = drive_letter.to_string(); 256 | if !Self::mount_esp(&mount_point) { 257 | return false; 258 | } 259 | 260 | let target_path = Path::new(&format!("{}:\\", drive_letter)).join(r"EFI/Lenovo/Logo"); 261 | 262 | // 如果目标路径存在,删除目标路径 263 | if target_path.exists() { 264 | if let Err(err) = std::fs::remove_dir_all(&target_path) { 265 | eprintln!("[!] Remove directory failed / 删除目录失败: {}", err); 266 | Self::unmount_esp(&mount_point); 267 | return false; 268 | } 269 | println!("[+] Logo directory deleted successfully / Logo目录删除成功"); 270 | } else { 271 | println!("[*] Logo directory does not exist / Logo目录不存在"); 272 | } 273 | 274 | Self::unmount_esp(&mount_point); 275 | true 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/lenlogo.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io; 3 | use std::io::Read; 4 | use std::path::Path; 5 | use std::str::FromStr; 6 | use efivar::efi::{Variable, VariableFlags}; 7 | use sha2::{Sha256, Digest}; 8 | use log::{debug, error, info}; 9 | 10 | use crate::esp_partition::{copy_file_to_esp, delete_logo_path}; 11 | 12 | #[cfg(target_os = "linux")] 13 | use crate::platform::linux::LinuxPlatform; 14 | 15 | /// 跨平台的EFI变量写入包装函数 16 | /// Linux下需要处理immutable属性,Windows下直接调用 17 | fn with_efi_var_writable(var_name: &str, f: F) -> Result<(), String> 18 | where 19 | F: FnOnce() -> Result<(), String>, 20 | { 21 | #[cfg(target_os = "linux")] 22 | { 23 | LinuxPlatform::with_efi_var_writable(var_name, f) 24 | } 25 | 26 | #[cfg(not(target_os = "linux"))] 27 | { 28 | // Windows和其他平台直接执行 29 | let _ = var_name; // 消除未使用变量警告 30 | f() 31 | } 32 | } 33 | 34 | pub(crate) struct PlatformInfo { 35 | pub(crate) enable: u8, 36 | pub(crate) width: u32, 37 | pub(crate) height: u32, 38 | pub(crate) version: u32, 39 | pub(crate) support: Vec<&'static str>, 40 | pub(crate) lbldesp_var: [u8; 10], 41 | pub(crate) lbldvc_var: [u8; 40], 42 | } 43 | 44 | impl Default for PlatformInfo { 45 | fn default() -> Self { 46 | Self { 47 | enable: 0, 48 | width: 0, 49 | height: 0, 50 | version: 0, 51 | support: Vec::new(), 52 | lbldesp_var: [0u8; 10], 53 | lbldvc_var: [0u8; 40], 54 | } 55 | } 56 | } 57 | 58 | impl PlatformInfo { 59 | pub(crate) fn get_info(&mut self) -> bool { 60 | let varman = efivar::system(); 61 | 62 | let esp_var = Variable::from_str("LBLDESP-871455D0-5576-4FB8-9865-AF0824463B9E").unwrap(); 63 | 64 | match varman.read(&esp_var) { 65 | Ok((esp_buffer, _attr)) => { 66 | if esp_buffer.len() != 10 { 67 | error!("read lbldesp_var failed: buffer length is not 10"); 68 | return false; 69 | } 70 | self.enable = esp_buffer[0]; 71 | self.width = u32::from_le_bytes(esp_buffer[1..5].try_into().unwrap()); 72 | self.height = u32::from_le_bytes(esp_buffer[5..9].try_into().unwrap()); 73 | self.support = Self::support_format(esp_buffer[9]); 74 | self.lbldesp_var = <[u8; 10]>::try_from(esp_buffer).unwrap(); 75 | }, 76 | Err(err) => { 77 | error!("read lbldesp_var failed: {}", err); 78 | return false; 79 | } 80 | } 81 | 82 | let dvc_var = Variable::from_str("LBLDVC-871455D1-5576-4FB8-9865-AF0824463C9F").unwrap(); 83 | match varman.read(&dvc_var) { 84 | Ok((dvc_buffer, _attr)) => { 85 | if dvc_buffer.len() != 40 { 86 | error!("read lbldvc_var failed: buffer length is not 40"); 87 | return false; 88 | } 89 | self.version = u32::from_le_bytes(dvc_buffer[0..4].try_into().unwrap()); 90 | self.lbldvc_var = <[u8; 40]>::try_from(dvc_buffer).unwrap(); 91 | }, 92 | Err(err) => { 93 | error!("read lbldvc_var failed: {}", err); 94 | return false; 95 | } 96 | } 97 | true 98 | } 99 | 100 | pub(crate) fn set_logo(&mut self, img_path: &String) -> bool { 101 | 102 | // 复制文件到ESP分区 103 | let file_path = Path::new(img_path); 104 | let file_extension = file_path.extension().unwrap().to_str().unwrap(); 105 | debug!("file_extension: {}", file_extension); 106 | 107 | let dst_path = format!(r"/EFI/Lenovo/Logo/mylogo_{}x{}.{}", self.width, self.height, file_extension); 108 | info!("target path: {}", dst_path); 109 | 110 | if copy_file_to_esp(img_path, &dst_path) == false { 111 | error!("copy file failed"); 112 | return false; 113 | } 114 | 115 | let mut varman = efivar::system(); 116 | 117 | // 修改logoinfo 118 | let mut esp_buffer = self.lbldesp_var.clone(); 119 | esp_buffer[0] = 1; 120 | let esp_var = Variable::from_str("LBLDESP-871455D0-5576-4FB8-9865-AF0824463B9E").unwrap(); 121 | 122 | // 在Linux下需要先移除immutable属性 123 | let write_result = with_efi_var_writable("LBLDESP-871455d0-5576-4fb8-9865-af0824463b9e", || { 124 | match varman.write(&esp_var, VariableFlags::from_bits(0x7).unwrap(), &esp_buffer) { 125 | Ok(rt) => { 126 | debug!("write lbldesp_var: {:?}", rt); 127 | Ok(()) 128 | }, 129 | Err(err) => { 130 | Err(format!("write lbldesp_var failed: {}", err)) 131 | } 132 | } 133 | }); 134 | 135 | match write_result { 136 | Ok(_) => { 137 | self.enable = 1; 138 | self.lbldesp_var = esp_buffer; 139 | }, 140 | Err(err) => { 141 | error!("{}", err); 142 | return false; 143 | } 144 | } 145 | 146 | // 修改logocheck 147 | let sha256_bytes; 148 | match calculate_sha256(img_path) { 149 | Ok(sha256) => { 150 | // 填充到new_logo_check中 151 | //let sha256_bytes = sha256.as_bytes(); 152 | // 将sha256十六进制字符串转化为十六进制序列 153 | sha256_bytes = hex::decode(sha256).unwrap(); 154 | } 155 | Err(e) => { 156 | error!("read error {}: {}", img_path, e); 157 | return false; 158 | }, 159 | } 160 | let mut dvc_buffer = self.lbldvc_var.clone(); 161 | dvc_buffer[4..36].clone_from_slice(&sha256_bytes); 162 | debug!("sha256_bytes: {:?}", sha256_bytes); 163 | debug!("dvc_buffer: {:?}", dvc_buffer); 164 | 165 | let dvc_var = Variable::from_str("LBLDVC-871455D1-5576-4FB8-9865-AF0824463C9F").unwrap(); 166 | 167 | // 在Linux下需要先移除immutable属性 168 | let write_result = with_efi_var_writable("LBLDVC-871455d1-5576-4fb8-9865-af0824463c9f", || { 169 | match varman.write(&dvc_var, VariableFlags::from_bits(0x7).unwrap(), &dvc_buffer) { 170 | Ok(rt) => { 171 | debug!("write lbldvc_var: {:?}", rt); 172 | Ok(()) 173 | }, 174 | Err(err) => { 175 | Err(format!("write lbldvc_var failed: {}", err)) 176 | } 177 | } 178 | }); 179 | 180 | match write_result { 181 | Ok(_) => { 182 | self.lbldvc_var = dvc_buffer; 183 | }, 184 | Err(err) => { 185 | error!("{}", err); 186 | return false; 187 | } 188 | } 189 | true 190 | } 191 | 192 | pub(crate) fn restore_logo(&mut self) -> bool { 193 | // 194 | let mut status = true; 195 | if !delete_logo_path() { 196 | error!("delete logo path failed"); 197 | status = false; 198 | } 199 | 200 | let mut varman = efivar::system(); 201 | // 修改logoinfo 202 | let mut esp_buffer = self.lbldesp_var.clone(); 203 | if esp_buffer[0] != 0 { 204 | esp_buffer[0] = 0; 205 | let esp_var = Variable::from_str("LBLDESP-871455D0-5576-4FB8-9865-AF0824463B9E").unwrap(); 206 | 207 | // 在Linux下需要先移除immutable属性 208 | let write_result = with_efi_var_writable("LBLDESP-871455d0-5576-4fb8-9865-af0824463b9e", || { 209 | match varman.write(&esp_var, VariableFlags::from_bits(0x7).unwrap(), &esp_buffer) { 210 | Ok(rt) => { 211 | debug!("write lbldesp_var: {:?}", rt); 212 | Ok(()) 213 | }, 214 | Err(err) => { 215 | Err(format!("write lbldesp_var failed: {}", err)) 216 | } 217 | } 218 | }); 219 | 220 | match write_result { 221 | Ok(_) => { 222 | self.lbldesp_var = esp_buffer; 223 | }, 224 | Err(err) => { 225 | error!("{}", err); 226 | status = false; 227 | } 228 | } 229 | } 230 | 231 | // 修改logocheck 232 | let mut dvc_buffer = self.lbldvc_var.clone(); 233 | if dvc_buffer[4..40] != [0u8; 36] { 234 | dvc_buffer[4..40].clone_from_slice(&[0u8; 36]); 235 | let dvc_var = Variable::from_str("LBLDVC-871455D1-5576-4FB8-9865-AF0824463C9F").unwrap(); 236 | 237 | let write_result = with_efi_var_writable("LBLDVC-871455d1-5576-4fb8-9865-af0824463c9f", || { 238 | match varman.write(&dvc_var, VariableFlags::from_bits(0x7).unwrap(), &dvc_buffer) { 239 | Ok(rt) => { debug!("write lbldvc_var: {:?}", rt); Ok(()) }, 240 | Err(err) => Err(format!("write lbldvc_var failed: {}", err)) 241 | } 242 | }); 243 | 244 | match write_result { 245 | Ok(_) => { 246 | self.lbldvc_var = dvc_buffer; 247 | }, 248 | Err(err) => { 249 | error!("{}", err); 250 | status = false; 251 | } 252 | } 253 | } 254 | status 255 | } 256 | 257 | 258 | fn support_format(support: u8) -> Vec<&'static str> { 259 | let mut support_types = Vec::new(); 260 | if support & 0x1 == 0x1 { 261 | support_types.push("jpg"); 262 | } 263 | if support & 0x2 == 0x2 { 264 | support_types.push("tga"); 265 | } 266 | if support & 0x4 == 0x4 { 267 | support_types.push("pcx"); 268 | } 269 | if support & 0x8 == 0x8 { 270 | support_types.push("gif"); 271 | } 272 | if support & 0x10 == 0x10 { 273 | support_types.push("bmp"); 274 | } 275 | if support & 0x20 == 0x20 { 276 | support_types.push("png"); 277 | } 278 | support_types 279 | } 280 | } 281 | 282 | fn calculate_sha256(file_path: &str) -> io::Result { 283 | let mut file = File::open(file_path)?; 284 | let mut sha256 = Sha256::new(); 285 | let mut buffer = [0; 1024]; 286 | 287 | loop { 288 | let bytes_read = file.read(&mut buffer)?; 289 | 290 | if bytes_read == 0 { 291 | break; 292 | } 293 | 294 | sha256.update(&buffer[..bytes_read]); 295 | } 296 | Ok(format!("{:x}", sha256.finalize())) 297 | } -------------------------------------------------------------------------------- /src/platform/linux.rs: -------------------------------------------------------------------------------- 1 | // Linux平台特定实现 2 | 3 | use std::path::Path; 4 | use std::process::Command; 5 | use std::fs::File; 6 | use std::os::unix::io::AsRawFd; 7 | use log::{info, error, warn, debug}; 8 | 9 | use super::{PlatformOps, EspPartitionOps}; 10 | 11 | // Linux下处理EFI变量immutable属性所需的常量 12 | const FS_IOC_GETFLAGS: libc::c_ulong = 0x80086601; 13 | const FS_IOC_SETFLAGS: libc::c_ulong = 0x40086602; 14 | const FS_IMMUTABLE_FL: u32 = 0x00000010; 15 | 16 | /// Linux平台实现 17 | pub struct LinuxPlatform; 18 | 19 | impl PlatformOps for LinuxPlatform { 20 | fn is_admin() -> bool { 21 | // Linux下检查是否为root用户(UID == 0) 22 | unsafe { 23 | libc::geteuid() == 0 24 | } 25 | } 26 | 27 | fn find_available_drive() -> Option { 28 | // Linux下不使用盘符,返回None 29 | // ESP分区通常挂载在 /boot/efi 或 /efi 30 | None 31 | } 32 | 33 | fn mount_esp(mount_point: &str) -> bool { 34 | // Linux下挂载ESP分区 35 | // 首先尝试找到ESP分区 36 | // 通常ESP分区类型为 vfat,标签可能是 EFI 或有特定的分区类型标识 37 | 38 | // 尝试挂载到指定的挂载点 39 | // mount -t vfat /dev/sdX1 /mnt/esp 40 | 41 | // 这里提供一个基本实现,实际使用时可能需要更复杂的逻辑 42 | let output = Command::new("mount") 43 | .arg("-t") 44 | .arg("vfat") 45 | .arg("-o") 46 | .arg("rw") 47 | .arg(Self::find_esp_partition().unwrap_or("/dev/sda1".to_string())) 48 | .arg(mount_point) 49 | .output(); 50 | 51 | match output { 52 | Ok(output) => { 53 | if output.status.success() { 54 | info!("Mounted ESP partition at {}", mount_point); 55 | true 56 | } else { 57 | error!("Failed to mount ESP partition: {}", String::from_utf8_lossy(&output.stderr)); 58 | false 59 | } 60 | } 61 | Err(e) => { 62 | error!("Failed to execute mount command: {}", e); 63 | false 64 | } 65 | } 66 | } 67 | 68 | fn unmount_esp(mount_point: &str) -> bool { 69 | // Linux下卸载分区 70 | // umount /mnt/esp 71 | let output = Command::new("umount") 72 | .arg(mount_point) 73 | .output(); 74 | 75 | match output { 76 | Ok(output) => { 77 | if output.status.success() { 78 | info!("Unmounted ESP partition"); 79 | true 80 | } else { 81 | error!("Failed to unmount ESP partition: {}", String::from_utf8_lossy(&output.stderr)); 82 | false 83 | } 84 | } 85 | Err(e) => { 86 | error!("Failed to execute umount command: {}", e); 87 | false 88 | } 89 | } 90 | } 91 | 92 | fn get_loading_icon() -> bool { 93 | // Linux下不支持Windows加载图标功能 94 | // 返回默认值false 95 | info!("Loading icon feature not supported on Linux"); 96 | false 97 | } 98 | 99 | fn set_loading_icon(_show_loading_icon: bool) -> bool { 100 | // Linux下不支持Windows加载图标功能 101 | info!("Loading icon feature not supported on Linux"); 102 | true // 返回true避免错误提示 103 | } 104 | 105 | fn get_system_font_path() -> Option { 106 | // Linux系统字体路径 107 | // 尝试常见的中文字体位置 108 | let font_paths = vec![ 109 | "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", 110 | "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", 111 | "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", 112 | "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", 113 | "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 114 | ]; 115 | 116 | for path in font_paths { 117 | if Path::new(path).exists() { 118 | return Some(path.to_string()); 119 | } 120 | } 121 | 122 | None 123 | } 124 | } 125 | 126 | impl LinuxPlatform { 127 | /// 查找ESP分区设备 128 | fn find_esp_partition() -> Option { 129 | // 使用 lsblk 或 blkid 查找ESP分区 130 | // ESP分区通常有 PARTTYPE="c12a7328-f81f-11d2-ba4b-00a0c93ec93b" 131 | 132 | let output = Command::new("lsblk") 133 | .args(&["-o", "NAME,PARTTYPE,MOUNTPOINT", "-n", "-l"]) 134 | .output(); 135 | 136 | if let Ok(output) = output { 137 | let stdout = String::from_utf8_lossy(&output.stdout); 138 | for line in stdout.lines() { 139 | // 查找ESP分区类型的GUID 140 | if line.contains("c12a7328-f81f-11d2-ba4b-00a0c93ec93b") { 141 | if let Some(device) = line.split_whitespace().next() { 142 | return Some(format!("/dev/{}", device)); 143 | } 144 | } 145 | } 146 | } 147 | 148 | // 如果找不到,尝试查找已挂载的EFI分区 149 | if let Ok(mounts) = std::fs::read_to_string("/proc/mounts") { 150 | for line in mounts.lines() { 151 | if line.contains("/boot/efi") || line.contains("vfat") && line.contains("efi") { 152 | if let Some(device) = line.split_whitespace().next() { 153 | return Some(device.to_string()); 154 | } 155 | } 156 | } 157 | } 158 | 159 | None 160 | } 161 | 162 | /// 在Linux下设置EFI变量文件的immutable属性 163 | pub fn set_efi_var_immutable(var_path: &Path, immutable: bool) -> Result<(), String> { 164 | use std::io; 165 | 166 | let file = File::open(var_path).map_err(|e| format!("Failed to open {}: {}", var_path.display(), e))?; 167 | let fd = file.as_raw_fd(); 168 | 169 | // 获取当前flags 170 | let mut flags: u32 = 0; 171 | let ret = unsafe { 172 | libc::ioctl(fd, FS_IOC_GETFLAGS, &mut flags as *mut u32) 173 | }; 174 | 175 | if ret < 0 { 176 | return Err(format!("Failed to get file flags: {}", io::Error::last_os_error())); 177 | } 178 | 179 | // 设置或清除immutable标志 180 | if immutable { 181 | flags |= FS_IMMUTABLE_FL; 182 | } else { 183 | flags &= !FS_IMMUTABLE_FL; 184 | } 185 | 186 | // 设置新的flags 187 | let ret = unsafe { libc::ioctl(fd, FS_IOC_SETFLAGS, &flags as *const u32) }; 188 | 189 | if ret < 0 { 190 | return Err(format!("Failed to set file flags: {}", io::Error::last_os_error())); 191 | } 192 | 193 | info!("Set immutable={} for {}", immutable, var_path.display()); 194 | Ok(()) 195 | } 196 | 197 | /// 在Linux下,写入EFI变量前后需要处理immutable属性 198 | pub fn with_efi_var_writable(var_name: &str, f: F) -> Result<(), String> 199 | where 200 | F: FnOnce() -> Result<(), String>, 201 | { 202 | let var_path = Path::new("/sys/firmware/efi/efivars").join(var_name); 203 | 204 | if !var_path.exists() { 205 | return Err(format!("EFI variable {} not found", var_name)); 206 | } 207 | 208 | // 移除immutable属性 209 | Self::set_efi_var_immutable(&var_path, false)?; 210 | 211 | // 执行写入操作 212 | let result = f(); 213 | 214 | // 恢复immutable属性 215 | if let Err(e) = Self::set_efi_var_immutable(&var_path, true) { 216 | warn!("Failed to restore immutable flag: {}", e); 217 | } 218 | 219 | result 220 | } 221 | } 222 | 223 | struct EspMountGuard<'a> { 224 | mount_point: &'a str, 225 | mounted: bool, 226 | } 227 | 228 | impl<'a> EspMountGuard<'a> { 229 | fn new(mount_point: &'a str) -> Result { 230 | if LinuxPlatform::mount_esp(mount_point) { 231 | Ok(Self { mount_point, mounted: true }) 232 | } else { 233 | Err(()) 234 | } 235 | } 236 | } 237 | 238 | impl Drop for EspMountGuard<'_> { 239 | fn drop(&mut self) { 240 | if self.mounted { 241 | if !LinuxPlatform::unmount_esp(self.mount_point) { 242 | warn!("Auto-unmount ESP failed at {}", self.mount_point); 243 | } 244 | } 245 | } 246 | } 247 | 248 | impl EspPartitionOps for LinuxPlatform { 249 | fn copy_file_to_esp(src: &str, dst: &str) -> bool { 250 | // 判断src是否为文件 251 | let src_path = Path::new(src); 252 | if !src_path.is_file() { 253 | error!("Source path is not a file"); 254 | return false; 255 | } 256 | 257 | // Linux下通常ESP分区挂载在 /boot/efi 258 | // 创建临时挂载点 259 | let mount_point = "/tmp/lenovo_esp_mount"; 260 | 261 | // 创建挂载点目录 262 | if let Err(e) = std::fs::create_dir_all(mount_point) { 263 | error!("Failed to create mount point: {}", e); 264 | return false; 265 | } 266 | 267 | // 挂载ESP分区(RAII自动卸载) 268 | let _guard = match EspMountGuard::new(mount_point) { Ok(g) => g, Err(_) => return false }; 269 | 270 | let target_path = Path::new(mount_point).join(dst.trim_start_matches('/')); 271 | 272 | // 如果目标上级路径存在,删除目标路径 273 | if let Some(parent) = target_path.parent() { 274 | if parent.exists() { 275 | if let Err(err) = std::fs::remove_dir_all(parent) { 276 | error!("Remove directory failed: {}", err); 277 | return false; 278 | } 279 | } 280 | } 281 | 282 | // 创建目标路径 283 | if let Some(parent) = target_path.parent() { 284 | if !parent.exists() { 285 | if let Err(err) = std::fs::create_dir_all(parent) { 286 | error!("Create directory failed: {}", err); 287 | return false; 288 | } 289 | } 290 | } 291 | 292 | // 将文件复制到目标路径 293 | if let Err(err) = std::fs::copy(src, &target_path) { 294 | error!("Copy file failed: {}", err); 295 | return false; 296 | } 297 | 298 | info!("File copied successfully: {}", target_path.display()); 299 | 300 | // 同步文件系统,确保写入 301 | Command::new("sync").output().ok(); 302 | 303 | true 304 | } 305 | 306 | fn delete_logo_path() -> bool { 307 | let mount_point = "/tmp/lenovo_esp_mount"; 308 | 309 | // 创建挂载点目录 310 | if let Err(e) = std::fs::create_dir_all(mount_point) { 311 | error!("Failed to create mount point: {}", e); 312 | return false; 313 | } 314 | 315 | // 挂载ESP分区(RAII自动卸载) 316 | let _guard = match EspMountGuard::new(mount_point) { Ok(g) => g, Err(_) => return false }; 317 | 318 | let target_path = Path::new(mount_point).join("EFI/Lenovo/Logo"); 319 | 320 | // 如果目标路径存在,删除目标路径 321 | if target_path.exists() { 322 | if let Err(err) = std::fs::remove_dir_all(&target_path) { 323 | error!("Remove directory failed: {}", err); 324 | return false; 325 | } 326 | info!("Logo directory deleted successfully"); 327 | } else { 328 | debug!("Logo directory does not exist"); 329 | } 330 | 331 | // 同步文件系统 332 | Command::new("sync").output().ok(); 333 | 334 | true 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")] // hide console window on Windows in release 2 | 3 | mod platform; 4 | mod lenlogo; 5 | mod esp_partition; 6 | mod i18n; 7 | 8 | use egui::FontId; 9 | use egui::RichText; 10 | use eframe::egui; 11 | use eframe::egui::Color32; 12 | use eframe::epaint::text::FontData; 13 | use egui::FontFamily::Proportional; 14 | use egui::TextStyle::{Body, Button, Heading, Monospace, Small}; 15 | use lenlogo::PlatformInfo; 16 | use platform::{PlatformOps, NativePlatform}; 17 | use poll_promise::Promise; 18 | use i18n::{t, Lang}; 19 | 20 | fn main() -> Result<(), eframe::Error> { 21 | let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) 22 | .format_timestamp(None) 23 | .format_target(false) 24 | .try_init(); 25 | let icon = include_bytes!("../assets/icon.png"); 26 | 27 | let options = eframe::NativeOptions { 28 | viewport: egui::ViewportBuilder::default() 29 | .with_inner_size([600.0, 420.0]) 30 | .with_min_inner_size([600.0, 420.0]) 31 | .with_icon(eframe::icon_data::from_png_bytes(icon).unwrap()), 32 | ..Default::default() 33 | }; 34 | eframe::run_native( 35 | "Lenovo UEFI Boot Logo Changer", 36 | options, 37 | Box::new(|cc| Ok(Box::new(MyApp::new(cc)))) 38 | ) 39 | } 40 | 41 | // 定义Logo操作的结果 42 | struct LogoOperationResult { 43 | success: bool, 44 | new_loading_icon_state: bool, 45 | // 返回更新后的平台信息 46 | enable: u8, 47 | lbldesp_var: [u8; 10], 48 | lbldvc_var: [u8; 40], 49 | } 50 | 51 | // 在后台线程执行设置Logo操作 52 | fn perform_set_logo_operation( 53 | img_path: String, 54 | show_loading_icon: bool, 55 | lbldesp_var: [u8; 10], 56 | lbldvc_var: [u8; 40], 57 | ) -> LogoOperationResult { 58 | // 先设置加载图标 59 | let loading_icon_result = NativePlatform::set_loading_icon(show_loading_icon); 60 | let new_loading_icon_state = NativePlatform::get_loading_icon(); 61 | 62 | if loading_icon_result { log::info!("Loading icon change success"); } else { log::error!("Loading icon change failed"); } 63 | 64 | // 执行设置Logo操作 65 | let mut temp_info = PlatformInfo::default(); 66 | temp_info.lbldesp_var = lbldesp_var; 67 | temp_info.lbldvc_var = lbldvc_var; 68 | temp_info.width = u32::from_le_bytes(lbldesp_var[1..5].try_into().unwrap()); 69 | temp_info.height = u32::from_le_bytes(lbldesp_var[5..9].try_into().unwrap()); 70 | 71 | let success = temp_info.set_logo(&img_path); 72 | 73 | if success { log::info!("Change logo success"); } else { log::error!("Change logo failed"); } 74 | 75 | // 在后台重新获取平台信息,避免在UI线程中读取 76 | let mut updated_info = PlatformInfo::default(); 77 | updated_info.get_info(); 78 | 79 | LogoOperationResult { 80 | success, 81 | new_loading_icon_state, 82 | enable: updated_info.enable, 83 | lbldesp_var: updated_info.lbldesp_var, 84 | lbldvc_var: updated_info.lbldvc_var, 85 | } 86 | } 87 | 88 | // 在后台线程执行恢复Logo操作 89 | fn perform_restore_logo_operation( 90 | lbldesp_var: [u8; 10], 91 | lbldvc_var: [u8; 40], 92 | ) -> LogoOperationResult { 93 | // 设置加载图标为启用 94 | let loading_icon_result = NativePlatform::set_loading_icon(true); 95 | let new_loading_icon_state = NativePlatform::get_loading_icon(); 96 | 97 | if loading_icon_result { log::info!("Restore loading icon success"); } else { log::error!("Restore loading icon failed"); } 98 | 99 | // 执行恢复Logo操作 100 | let mut temp_info = PlatformInfo::default(); 101 | temp_info.lbldesp_var = lbldesp_var; 102 | temp_info.lbldvc_var = lbldvc_var; 103 | 104 | let success = temp_info.restore_logo(); 105 | 106 | if success { log::info!("Restore logo success"); } else { log::error!("Restore logo failed"); } 107 | 108 | // 在后台重新获取平台信息,避免在UI线程中读取 109 | let mut updated_info = PlatformInfo::default(); 110 | updated_info.get_info(); 111 | 112 | LogoOperationResult { 113 | success, 114 | new_loading_icon_state, 115 | enable: updated_info.enable, 116 | lbldesp_var: updated_info.lbldesp_var, 117 | lbldvc_var: updated_info.lbldvc_var, 118 | } 119 | } 120 | 121 | #[derive(Default)] 122 | struct MyApp { 123 | language: String, 124 | is_admin:bool, 125 | is_support:bool, 126 | is_loading_icon: bool, 127 | platform_info: PlatformInfo, 128 | last_set_logo:i8, 129 | last_restore_logo:i8, 130 | set_loading_icon: bool, 131 | picked_path: Option, 132 | // Promise用于异步操作 133 | set_logo_promise: Option>, 134 | restore_logo_promise: Option>, 135 | // 添加待处理标志,用于在下一帧启动异步操作 136 | pending_set_logo: bool, 137 | pending_restore_logo: bool, 138 | } 139 | 140 | impl eframe::App for MyApp { 141 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 142 | if self.is_admin { 143 | self.show_main_ui(ctx); 144 | } 145 | else { 146 | self.show_admin_prompt_ui(ctx); 147 | } 148 | } 149 | } 150 | 151 | impl MyApp { 152 | fn new(cc: &eframe::CreationContext<'_>) -> Self { 153 | log::debug!("Start MyApp::new"); 154 | setup_custom_fonts(&cc.egui_ctx); 155 | let is_admin = NativePlatform::is_admin(); 156 | let mut platform_info = PlatformInfo::default(); 157 | let mut is_support = false; 158 | if is_admin { 159 | is_support = platform_info.get_info(); 160 | } 161 | let language = String::from("en"); 162 | let is_loading_icon = NativePlatform::get_loading_icon(); 163 | let set_loading_icon = is_loading_icon; 164 | 165 | Self { 166 | language, 167 | is_admin, 168 | is_support, 169 | is_loading_icon, 170 | set_loading_icon, 171 | platform_info, 172 | ..Default::default() 173 | } 174 | } 175 | 176 | fn show_main_ui(&mut self, ctx: &egui::Context) { 177 | let lang = Lang::from_code(&self.language); 178 | egui::CentralPanel::default().show(ctx, |ui| { 179 | ui.horizontal(|ui| { 180 | ui.label(format!("{} : ", t(lang, "language"))); 181 | ui.radio_value(&mut self.language, String::from("en"), t(lang, "english").as_ref()); 182 | ui.radio_value(&mut self.language, String::from("zh"), t(lang, "chinese").as_ref()); 183 | }); 184 | ui.separator(); 185 | 186 | if self.is_support { 187 | ui.colored_label(Color32::LIGHT_GREEN, t(lang, "supported")); 188 | 189 | ui.separator(); 190 | if self.platform_info.enable != 0 { 191 | ui.colored_label(Color32::LIGHT_GREEN, t(lang, "logo_enabled")); 192 | } 193 | else { 194 | ui.colored_label(Color32::LIGHT_RED, t(lang, "logo_disabled")); 195 | } 196 | 197 | ui.label(format!("{} : {}x{}", t(lang, "max_image_size"), self.platform_info.width, self.platform_info.height)); 198 | // ui.label(format!("Support Format / 支持的图片格式 : {}", self.platform_info.support.join(" / "))); 199 | ui.label(format!("{} : {}", t(lang, "supported_formats"), self.platform_info.support.join(" / "))); 200 | ui.label(format!("{} : {:x}", t(lang, "version"), self.platform_info.version)); 201 | 202 | ui.separator(); 203 | 204 | if !self.platform_info.support.is_empty() { 205 | // 只在Windows平台显示加载图标选项 206 | #[cfg(target_os = "windows")] 207 | ui.checkbox(&mut self.set_loading_icon, t(lang, "show_windows_loading").as_ref()); 208 | 209 | if ui.button(t(lang, "pick_image").as_ref()).clicked() { 210 | if let Some(path) = rfd::FileDialog::new() 211 | .add_filter("Image", &*self.platform_info.support) 212 | .pick_file() { 213 | self.picked_path = Some(path.display().to_string()); 214 | } 215 | } 216 | } 217 | 218 | if let Some(picked_path) = &self.picked_path { 219 | if self.platform_info.version == 0x20003 { 220 | ui.horizontal(|ui| { 221 | ui.label(t(lang, "picked_image").as_ref()); 222 | ui.monospace(picked_path); 223 | }); 224 | if ui.button(RichText::new(t(lang, "change_logo_btn").to_string()).color(Color32::RED)).clicked() && self.set_logo_promise.is_none() && !self.pending_set_logo { 225 | self.last_restore_logo = 0; 226 | self.last_set_logo = 0; 227 | // 标记为待处理,在下一帧启动异步操作 228 | self.pending_set_logo = true; 229 | ctx.request_repaint(); 230 | } 231 | 232 | // 在单独的逻辑块中启动异步操作,避免在按钮点击时立即执行 233 | if self.pending_set_logo && self.set_logo_promise.is_none() { 234 | // 捕获需要的数据 235 | let img_path = picked_path.clone(); 236 | let show_loading_icon = self.set_loading_icon; 237 | let lbldesp_var = self.platform_info.lbldesp_var; 238 | let lbldvc_var = self.platform_info.lbldvc_var; 239 | 240 | // 在后台线程执行操作 241 | self.set_logo_promise = Some(Promise::spawn_thread("set_logo", move || { 242 | perform_set_logo_operation(img_path, show_loading_icon, lbldesp_var, lbldvc_var) 243 | })); 244 | self.pending_set_logo = false; 245 | } 246 | 247 | // 检查Promise是否完成 248 | if let Some(promise) = &self.set_logo_promise { 249 | if let Some(result) = promise.ready() { 250 | // 操作完成,更新状态(从后台线程返回的结果更新,不在UI线程读取) 251 | self.is_loading_icon = result.new_loading_icon_state; 252 | self.set_loading_icon = result.new_loading_icon_state; 253 | self.last_set_logo = if result.success { 1 } else { -1 }; 254 | 255 | // 使用后台线程返回的平台信息,避免在UI线程调用get_info() 256 | self.platform_info.enable = result.enable; 257 | self.platform_info.lbldesp_var = result.lbldesp_var; 258 | self.platform_info.lbldvc_var = result.lbldvc_var; 259 | 260 | // 清除Promise 261 | self.set_logo_promise = None; 262 | } else { 263 | // 正在处理中,显示spinner 264 | ui.horizontal(|ui| { ui.spinner(); ui.label(t(lang, "setting_logo_wait").as_ref()); }); 265 | ctx.request_repaint(); // 继续请求重绘以更新UI 266 | } 267 | } 268 | } 269 | } 270 | 271 | match self.last_set_logo { 272 | 1 => { 273 | ui.colored_label(Color32::LIGHT_GREEN, t(lang, "change_logo_success")); 274 | }, 275 | -1 => { 276 | ui.colored_label(Color32::LIGHT_RED, t(lang, "change_logo_failed")); 277 | }, 278 | _ => {} 279 | } 280 | 281 | ui.separator(); 282 | if ui.button(t(lang, "restore_logo_btn").as_ref()).clicked() && self.restore_logo_promise.is_none() && !self.pending_restore_logo { 283 | self.last_restore_logo = 0; 284 | self.last_set_logo = 0; 285 | // 标记为待处理,在下一帧启动异步操作 286 | self.pending_restore_logo = true; 287 | ctx.request_repaint(); 288 | } 289 | 290 | // 在单独的逻辑块中启动异步操作,避免在按钮点击时立即执行 291 | if self.pending_restore_logo && self.restore_logo_promise.is_none() { 292 | // 捕获需要的数据 293 | let lbldesp_var = self.platform_info.lbldesp_var; 294 | let lbldvc_var = self.platform_info.lbldvc_var; 295 | 296 | // 在后台线程执行操作 297 | self.restore_logo_promise = Some(Promise::spawn_thread("restore_logo", move || { 298 | perform_restore_logo_operation(lbldesp_var, lbldvc_var) 299 | })); 300 | self.pending_restore_logo = false; 301 | } 302 | 303 | // 检查Promise是否完成 304 | if let Some(promise) = &self.restore_logo_promise { 305 | if let Some(result) = promise.ready() { 306 | // 操作完成,更新状态(从后台线程返回的结果更新,不在UI线程读取) 307 | self.is_loading_icon = result.new_loading_icon_state; 308 | self.set_loading_icon = result.new_loading_icon_state; 309 | self.last_restore_logo = if result.success { 1 } else { -1 }; 310 | 311 | // 使用后台线程返回的平台信息,避免在UI线程调用get_info() 312 | self.platform_info.enable = result.enable; 313 | self.platform_info.lbldesp_var = result.lbldesp_var; 314 | self.platform_info.lbldvc_var = result.lbldvc_var; 315 | self.is_support = result.enable != 0 || result.success; 316 | 317 | // 清除Promise 318 | self.restore_logo_promise = None; 319 | } else { 320 | // 正在处理中,显示spinner 321 | ui.horizontal(|ui| { ui.spinner(); ui.label(t(lang, "restoring_logo_wait").as_ref()); }); 322 | ctx.request_repaint(); // 继续请求重绘以更新UI 323 | } 324 | } 325 | match self.last_restore_logo { 326 | 1 => { 327 | ui.colored_label(Color32::LIGHT_GREEN, t(lang, "restore_logo_success")); 328 | }, 329 | -1 => { 330 | ui.colored_label(Color32::LIGHT_RED, t(lang, "restore_logo_failed")); 331 | }, 332 | _ => {} 333 | } 334 | 335 | ui.separator(); 336 | 337 | ui.allocate_space(egui::vec2(0.0, (ui.available_height() - 18.0).max(0.0))); 338 | ui.vertical_centered(|ui| { 339 | use egui::special_emojis::GITHUB; 340 | ui.label(RichText::new( 341 | format!("{GITHUB} @chnzzh | MIT License") 342 | ).text_style(egui::TextStyle::Small)).on_hover_text("https://github.com/chnzzh/lenovo-logo-changer"); 343 | }); 344 | } 345 | else { 346 | ui.label(t(lang, "unsupported").as_ref()); 347 | }}); 348 | } 349 | 350 | fn show_admin_prompt_ui(&mut self, ctx: &egui::Context) { 351 | let lang = Lang::from_code(&self.language); 352 | egui::CentralPanel::default().show(ctx, |ui| { 353 | ui.label(t(lang, "admin_required").as_ref()); 354 | }); 355 | } 356 | 357 | } 358 | 359 | fn setup_custom_fonts(ctx: &egui::Context) { 360 | // Start with the default fonts (we will be adding to them rather than replacing them). 361 | let mut fonts = egui::FontDefinitions::default(); 362 | 363 | // Install my own assets (maybe supporting non-latin characters). 364 | // .ttf and .otf files supported. 365 | 366 | // 尝试获取系统字体路径(Windows特定) 367 | if let Some(font_path) = NativePlatform::get_system_font_path() { 368 | if let Ok(font) = std::fs::read(&font_path) { 369 | fonts.font_data.insert( 370 | "my_font".to_owned(), 371 | std::sync::Arc::new(FontData::from_owned(font)), 372 | ); 373 | 374 | fonts.families.get_mut(&Proportional).unwrap() 375 | .insert(0, "my_font".to_owned()); 376 | 377 | fonts.families.get_mut(&egui::FontFamily::Monospace).unwrap() 378 | .insert(0, "my_font".to_owned()); 379 | } else { log::warn!("Failed to load system font from: {}", font_path); } 380 | } 381 | 382 | ctx.set_fonts(fonts); 383 | 384 | let mut style = (*ctx.style()).clone(); 385 | style.text_styles = [ 386 | (Heading, FontId::new(30.0, Proportional)), 387 | (Body, FontId::new(18.0, Proportional)), 388 | (Monospace, FontId::new(18.0, Proportional)), 389 | (Button, FontId::new(18.0, Proportional)), 390 | (Small, FontId::new(15.0, Proportional)), 391 | ].into(); 392 | ctx.set_style(style); 393 | } 394 | --------------------------------------------------------------------------------