mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-06 17:15:42 -08:00
Compare commits
49 Commits
v2.3.1
...
d40ce9f6c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40ce9f6c5 | ||
|
|
447ff30f61 | ||
|
|
5e9ef85451 | ||
|
|
96599b1838 | ||
|
|
611ab30db3 | ||
|
|
ffa3e335ea | ||
|
|
a1f6297759 | ||
|
|
afd0780b61 | ||
|
|
320e154457 | ||
|
|
785307c0e2 | ||
|
|
be62caa400 | ||
|
|
734ea21308 | ||
|
|
1946765680 | ||
|
|
79ee253108 | ||
|
|
82abb0c120 | ||
|
|
830db410ea | ||
|
|
1e36d7e388 | ||
|
|
9038e20cd2 | ||
|
|
9a0d4737e1 | ||
|
|
fa6fae3aa3 | ||
|
|
90fb8e47c1 | ||
|
|
c826149183 | ||
|
|
af4f99aab6 | ||
|
|
8a97382a2e | ||
|
|
8e6466990a | ||
|
|
6f94f6bc28 | ||
|
|
bc600a6c2f | ||
|
|
022ea9b93b | ||
|
|
7d7d4c2aa4 | ||
|
|
f1c8b11d6b | ||
|
|
a0553856f0 | ||
|
|
ffa29f6ffc | ||
|
|
b9e23c91a3 | ||
|
|
697c57355b | ||
|
|
da16365508 | ||
|
|
665a4732ca | ||
|
|
39596aadfc | ||
|
|
1a5b1df064 | ||
|
|
ea70f3ce79 | ||
|
|
a44b929012 | ||
|
|
9948e5a306 | ||
|
|
518d7c8a97 | ||
|
|
c6108ffd62 | ||
|
|
6dc447a8ce | ||
|
|
5ec17a49e8 | ||
|
|
7b7309a255 | ||
|
|
a82646a822 | ||
|
|
294da9c069 | ||
|
|
4096002eb2 |
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report if you encounter a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What happens?**
|
||||
Describe the bug.
|
||||
|
||||
**How this happens?**
|
||||
Explain exactly the steps you did:
|
||||
1. Download HackBGRT-(version).zip and extract all files.
|
||||
2. Start setup.
|
||||
3. Select ...
|
||||
4. ...
|
||||
|
||||
**Log file**
|
||||
Run the setup again and select menu option `L` (or add parameter `show-boot-log` on command line). Attach the `setup.log` to this report.
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -2,6 +2,46 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2.5.2 - 2025-04-13
|
||||
|
||||
### Fixed
|
||||
- Try to avoid mistaking C: for ESP.
|
||||
- Don't require Windows boot loader entry if it's not needed.
|
||||
|
||||
### Changed
|
||||
- Allow overriding ESP path.
|
||||
- Try to load `config.txt` from the current dir, to support multiple configurations.
|
||||
- Improve output and README for various situations.
|
||||
|
||||
## 2.5.1 - 2024-08-18
|
||||
|
||||
### Changed
|
||||
- Update *shim* to 15.8.
|
||||
|
||||
## 2.5.0 - 2024-06-21
|
||||
|
||||
### Changed
|
||||
- Properly handle skip-shim with enable-overwrite.
|
||||
- Improve instructions (documentation).
|
||||
- Improve error reporting and logging.
|
||||
|
||||
## 2.4.1 - 2024-04-11
|
||||
|
||||
### Fixed
|
||||
- Report better if BCDEdit is unable to operate.
|
||||
- Improve support for non-BCDEdit boot entries.
|
||||
- Remove old version before copying any new files.
|
||||
|
||||
## 2.4.0 - 2023-12-31
|
||||
|
||||
### Fixed
|
||||
- Fix BCDEdit boot entries to avoid *shim* error messages.
|
||||
- Combine BCDEdit and own code to create boot entries more reliably.
|
||||
|
||||
### Changed
|
||||
- Clear the screen to wipe the vendor logo as soon as possible.
|
||||
- Image paths in `config.txt` may be relative (just file names).
|
||||
|
||||
## 2.3.1 - 2023-11-27
|
||||
|
||||
### Fixed
|
||||
|
||||
22
Makefile
22
Makefile
@@ -10,8 +10,22 @@ GNUEFI_INC = gnu-efi/inc
|
||||
|
||||
FILES_C = src/main.c src/util.c src/types.c src/config.c src/sbat.c src/efi.c
|
||||
FILES_H = $(wildcard src/*.h)
|
||||
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs
|
||||
GIT_DESCRIBE := $(firstword $(GIT_DESCRIBE) $(shell git describe --tags) unknown)
|
||||
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs src/EfiBootEntries.cs
|
||||
|
||||
# Generate version number from git describe.
|
||||
# In the numeric form, add the number of commits as the last part.
|
||||
# (Add .1 for uncommitted changes.)
|
||||
GIT_DESCRIBE := $(firstword $(GIT_DESCRIBE) $(shell git describe --tags --dirty=-1-dirty) unknown)
|
||||
GIT_DESCRIBE_PARTS := $(subst -, ,$(patsubst v%,%,$(GIT_DESCRIBE))) 0
|
||||
GIT_DESCRIBE_NUMERIC := $(firstword $(GIT_DESCRIBE_PARTS)).$(word 2,$(GIT_DESCRIBE_PARTS))
|
||||
|
||||
define GIT_DESCRIBE_CS
|
||||
public class GIT_DESCRIBE {
|
||||
public const string data = "$(GIT_DESCRIBE)";
|
||||
public const string numeric = "$(GIT_DESCRIBE_NUMERIC)";
|
||||
}
|
||||
endef
|
||||
|
||||
CFLAGS += '-DGIT_DESCRIBE_W=L"$(GIT_DESCRIBE)"' '-DGIT_DESCRIBE="$(GIT_DESCRIBE)"'
|
||||
RELEASE_NAME = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||
|
||||
@@ -45,10 +59,10 @@ release/$(RELEASE_NAME).zip: release/$(RELEASE_NAME)
|
||||
(cd release; 7z a -mx=9 "$(RELEASE_NAME).zip" "$(RELEASE_NAME)" -bd -bb1)
|
||||
|
||||
src/GIT_DESCRIBE.cs: $(FILES_CS) $(FILES_C) $(FILES_H)
|
||||
echo 'public class GIT_DESCRIBE { public const string data = "$(GIT_DESCRIBE)"; }' > $@
|
||||
$(file > $@,$(GIT_DESCRIBE_CS))
|
||||
|
||||
setup.exe: $(FILES_CS) src/GIT_DESCRIBE.cs
|
||||
csc /nologo /define:GIT_DESCRIBE /out:$@ $^
|
||||
csc -nologo -define:GIT_DESCRIBE -out:$@ $^
|
||||
|
||||
certificate.cer pki:
|
||||
@echo
|
||||
|
||||
82
README.md
82
README.md
@@ -6,6 +6,8 @@ HackBGRT is intended as a boot logo changer for UEFI-based Windows systems.
|
||||
|
||||
When booting on a UEFI-based computer, Windows may show a vendor-defined logo which is stored on the UEFI firmware in a section called Boot Graphics Resource Table (BGRT). It's usually very difficult to change the image permanently, but a custom UEFI application may be used to overwrite it during the boot. HackBGRT does exactly that.
|
||||
|
||||
**Note:** The original logo is often visible for a moment before HackBGRT is started. This is expected, please do not report this "bug". This can't be changed without modifying computer firmware, which this project will not do.
|
||||
|
||||
## Usage
|
||||
|
||||
**Important:** If you mess up the installation, your system may become unbootable! Create a rescue disk before use. This software comes with no warranty. Use at your own risk.
|
||||
@@ -24,21 +26,23 @@ The *shim* boot loader is maintained by Red Hat, Inc, and the included signed co
|
||||
|
||||
* Get the latest release from the Releases page.
|
||||
* Start `setup.exe` and follow the instructions.
|
||||
* The installer will launch Paint for editing the image.
|
||||
* If Windows later restores the original boot loader, just reinstall.
|
||||
* If you wish to change the image or other configuration, just reinstall.
|
||||
* The installer will launch Paint for editing the image, or you can edit it otherwise.
|
||||
* For advanced settings, edit `config.txt` before installing. No extra support provided!
|
||||
* After installing, read the instructions in [shim.md](shim.md) and reboot your computer.
|
||||
* Read the instructions in [shim.md](shim.md).
|
||||
* Check the common [troubleshooting](#troubleshooting) to be prepared.
|
||||
* Reboot your computer.
|
||||
* If Windows later restores the original boot loader, just reinstall.
|
||||
* If you wish to change the image or configuration later, choose the option to only install files.
|
||||
|
||||
### Quiet (batch) installation
|
||||
|
||||
* Edit the `config.txt` and `splash.bmp` (or any other images) to your needs.
|
||||
* Run `setup.exe batch COMMANDS` as administrator, with some of the following commands:
|
||||
* `install` – copy the files but don't enable.
|
||||
* `enable-entry` – create a new EFI boot entry.
|
||||
* `disable-entry` – disable the EFI boot entry.
|
||||
* `enable-bcdedit` – use `bcdedit` to create a new EFI boot entry.
|
||||
* `disable-bootmgr` – use `bcdedit` to disable the EFI boot entry.
|
||||
* `disable-bcdedit` – use `bcdedit` to disable the EFI boot entry.
|
||||
* `enable-entry` – write NVRAM to create a new EFI boot entry.
|
||||
* `disable-entry` – write NVRAM to disable the EFI boot entry.
|
||||
* `enable-overwrite` – overwrite the MS boot loader.
|
||||
* `disable-overwrite` – restore the MS boot loader.
|
||||
* `skip-shim` – skip *shim* when installing.
|
||||
@@ -48,7 +52,10 @@ The *shim* boot loader is maintained by Red Hat, Inc, and the included signed co
|
||||
* `disable` – run all relevant `disable-*` commands.
|
||||
* `uninstall` – disable and remove completely.
|
||||
* `show-boot-log` – show the debug log collected during boot (if `log=1` is set in `config.txt`).
|
||||
* For example, run `setup.exe batch install allow-secure-boot enable-overwrite` to copy files and overwrite the MS boot loader regardless of Secure Boot status.
|
||||
* `arch=...` – force architecture.
|
||||
* `esp=...` – force EFI System Partition path.
|
||||
* `dry-run` – skip actual changes.
|
||||
* For example, `setup.exe batch disable install enable-bcdedit` would disable any previous installation, then install the files and create the EFI boot entry with `bcdedit`.
|
||||
|
||||
### Multi-boot configurations
|
||||
|
||||
@@ -64,23 +71,72 @@ If you need it for other systems as well:
|
||||
|
||||
To install purely on Linux, you can install with `setup.exe dry-run` and then manually copy files from `dry-run/EFI` to your `[EFI System Partition]/EFI`. For further instructions, consult the documentation of your own Linux system.
|
||||
|
||||
HackBGRT tries to read its configuration from the same directory where it's installed, so you can even make (manually) multiple installations in different directories.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration options are described in `config.txt`, which the installer copies into `[EFI System Partition]\EFI\HackBGRT\config.txt`.
|
||||
|
||||
## Images
|
||||
|
||||
The image path can be changed in the configuration file. The default path is `[EFI System Partition]\EFI\HackBGRT\splash.bmp`.
|
||||
If you only need one image, just edit `splash.bmp` to your needs.
|
||||
|
||||
The installer copies and converts files whose `path` starts with `\EFI\HackBGRT\`. For example, to use a file named `my.jpg`, copy it in the installer folder (same folder as `setup.exe`) and set the image path in `config.txt` to `path=\EFI\HackBGFT\my.jpg`.
|
||||
Advanced users may edit the `config.txt` to define multiple images, in which case one is picked at random. The installer copies and converts the images. For example, to use a file named `my.jpg`, copy it in the installer folder (same folder as `setup.exe`) and set the image path in `config.txt` to `path=my.jpg` before running the installer.
|
||||
|
||||
If you copy an image file to ESP manually, note that the image must be a 24-bit BMP file with a 54-byte header. That's a TrueColor BMP3 in Imagemagick, or 24-bit BMP/DIB in Microsoft Paint.
|
||||
|
||||
Advanced users may edit the `config.txt` to define multiple images, in which case one is picked at random.
|
||||
## Troubleshooting
|
||||
|
||||
## Recovery
|
||||
### BCDEdit failed
|
||||
|
||||
If something breaks and you can't boot to Windows, you need to use the Windows installation disk (or recovery disk) to fix boot issues.
|
||||
You can first try the other installation option in the menu. If it doesn't work either, your computer might have a problem. Open Command Prompt and figure out why `bcdedit /enum firmware` fails. In some cases, disabling antivirus, checking the hard disk or searching for 'how to fix 0x800703EE' may help.
|
||||
|
||||
### Verification failed, Security violation
|
||||
|
||||
This is part of the setup on first boot. Make sure you have read and understood [shim.md](shim.md).
|
||||
|
||||
### Boot is slow, boot is stuck, just spinning
|
||||
|
||||
Sometimes the first boot is very slow (multiple minutes) for an unknown reason. Wait patiently until you get into Windows. Try to reboot at least a few times to see if it gets any better. It it does not, there's not much else to do than give up.
|
||||
|
||||
### Image is not visible, "nothing happens"
|
||||
|
||||
Run the setup again and select the option to check the boot log, marked with `BOOT LOG START` in the log file. Continue troubleshooting according to the log contents:
|
||||
|
||||
#### Boot log is empty
|
||||
|
||||
If the log is empty, then HackBGRT is not in use. Many computers now have a security feature which causes this problem: the computer prevents enabling HackBGRT automatically, instead it resets a certain setting (BootOrder) on reboot and skips the newly-installed HackBGRT.
|
||||
|
||||
You have to fix this manually. (After all, the security feature is specifically designed to prevent automatic changes.)
|
||||
|
||||
1. Run the setup again.
|
||||
2. Select the option "boot to UEFI setup".
|
||||
3. After a reboot, you should get into your computer's own setup utility (UEFI or Firmware settings, or so-called "BIOS").
|
||||
4. Find boot options and the list of boot entries.
|
||||
5. Select HackBGRT as the default boot entry (before Windows Boot Loader).
|
||||
|
||||
The setup utility is different for each computer and manufacturer, so search online for "[computer model] UEFI setup" or "firmware setup" for images and instructions.
|
||||
|
||||
Some people report that HackBGRT is not visible in the computer settings. That's unfortunately a problem with your computer, and you should ask your computer manufacturer how to edit boot entries inside your computer settings. HackBGRT needs to boot `\EFI\HackBGRT\loader.efi`.
|
||||
|
||||
If all else fails and you are sure about your computer skills, you can try the legacy installation method. The method bypasses this particular problem but may cause very serious problems if configured incorrectly.
|
||||
|
||||
#### Boot log is not empty
|
||||
|
||||
If the log shows that HackBGRT has been run during boot, the problem is usually in your configuration file or image. Try to reinstall HackBGRT with the default configuration and image.
|
||||
|
||||
If the default logo works, try again with your custom image. Make sure that the image has a reasonable size and position so that it fits the resolution which HackBGRT reports during boot. The resolution may be lower than your desktop resolution.
|
||||
|
||||
When you get your image working with the default configuration, you can do any other necessary changes to `config.txt`.
|
||||
|
||||
If the default logo does not work, check the boot log again to see if there is some obvious error.
|
||||
You may report an issue and attach the `setup.log` file.
|
||||
|
||||
### Impossible to boot at all
|
||||
|
||||
If you used the default installation method, then your Windows boot loader is still in place and you should be able to access UEFI Setup ("BIOS setup") or boot loader list by some key combination right after powering on your computer. There you can choose the `Windows Boot Loader` and continue as usual to uninstall HackBGRT.
|
||||
|
||||
If you selected the legacy installation method which overwrites Windows boot loader, then you need to use the Windows installation disk (or recovery disk) to fix boot issues.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
14
config.txt
14
config.txt
@@ -16,16 +16,14 @@ boot=MS
|
||||
# - "keep" to keep the firmware logo. Also keeps coordinates by default.
|
||||
# - "remove" to remove the BGRT. Makes x and y meaningless.
|
||||
# - "black" to use only a black image. Makes x and y meaningless.
|
||||
# - "path=..." to read a BMP file.
|
||||
# * NOTE: For path=\EFI\HackBGRT\*, the installer will copy and convert the file if necessary.
|
||||
# * NOTE: For other paths, make sure that the file is a 24-bit BMP file with a 54-byte header.
|
||||
# * NOTE: The file must be on the EFI System Partition. Do not add a drive letter!
|
||||
# - "path=file.bmp" to read an image file.
|
||||
# * NOTE: The installer can copy and convert BMP, PNG, JPEG, GIF.
|
||||
# Examples:
|
||||
# - image=remove
|
||||
# - image=black
|
||||
# - image= x=0 y=-200 path=\EFI\HackBGRT\topimage.bmp
|
||||
# - image= n=1 o=90 path=\EFI\HackBGRT\sideways.bmp
|
||||
# - image= n=50 y=999999 o=keep path=\EFI\HackBGRT\probable.bmp
|
||||
# - image= x=0 y=-200 path=topimage.bmp
|
||||
# - image= n=1 o=90 path=sideways.bmp
|
||||
# - image= n=50 y=999999 o=keep path=probable.bmp
|
||||
# The above examples together would produce
|
||||
# - 1/54 chance for the default OS logo
|
||||
# - 1/54 chance for black screen
|
||||
@@ -33,7 +31,7 @@ boot=MS
|
||||
# - 1/54 chance for splash.bmp, centered, orientation set to 90 degrees
|
||||
# - 50/54 chance for probable.bmp, at the bottom edge, explicitly default orientation
|
||||
# Default: just one image.
|
||||
image= y=-200 path=\EFI\HackBGRT\splash.bmp
|
||||
image= y=-200 path=splash.bmp
|
||||
|
||||
# Preferred resolution. Use 0x0 for maximum and -1x-1 for original.
|
||||
resolution=0x0
|
||||
|
||||
2
gnu-efi
2
gnu-efi
Submodule gnu-efi updated: 965f557ab7...74bd9b60ba
@@ -1,55 +1,30 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: shim
|
||||
Upstream-Contact: Peter Jones <pjones@redhat.com>
|
||||
Source: https://github.com/rhboot/shim
|
||||
Copyright 2012 Red Hat, Inc <mjg@redhat.com>
|
||||
|
||||
Files: *
|
||||
Copyright: 2012 Red Hat, Inc
|
||||
2009-2012 Intel Corporation
|
||||
License: BSD-2-Clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Files: debian/po/cs.po
|
||||
Copyright: 2018 Michal Simunek <michal.simunek@gmail.com>
|
||||
License: BSD-2-Clause
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Files: debian/po/de.po
|
||||
Copyright: 2017, 2018 Markus Hiereth <markus.hiereth@freenet.de>
|
||||
License: BSD-2-Clause
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
Files: debian/po/fr.po
|
||||
Copyright: 2017, 2018 Alban Vidal <alban.vidal@zordhak.fr>
|
||||
License: BSD-2-Clause
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Files: debian/po/nl.po
|
||||
Copyright: 2017, 2018 Frans Spiesschaert <Frans.Spiesschaert@yucom.be>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: debian/po/pt.po
|
||||
Copyright: 2017, 2018 Rui Branco <ruipb@debianpt.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
License: BSD-2-Clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
.
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
.
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Significant portions of this code are derived from Tianocore
|
||||
(http://tianocore.sf.net) and are Copyright 2009-2012 Intel
|
||||
Corporation.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
264
src/Efi.cs
264
src/Efi.cs
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Win32;
|
||||
|
||||
/**
|
||||
@@ -64,81 +65,6 @@ public class Efi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about an EFI boot entry.
|
||||
*/
|
||||
public class BootEntryData {
|
||||
public UInt32 Attributes;
|
||||
public string Label;
|
||||
public class DevicePathNode {
|
||||
public byte Type, SubType;
|
||||
public byte[] Data;
|
||||
public DevicePathNode(byte[] data) {
|
||||
Type = data[0];
|
||||
SubType = data[1];
|
||||
Data = data.Skip(4).ToArray();
|
||||
}
|
||||
public byte[] ToBytes() {
|
||||
var len = Data.Length + 4;
|
||||
return new byte[] { Type, SubType, (byte)(len & 0xff), (byte)(len >> 8) }.Concat(Data).ToArray();
|
||||
}
|
||||
}
|
||||
public List<DevicePathNode> DevicePathNodes;
|
||||
public byte[] Arguments;
|
||||
|
||||
public BootEntryData(byte[] data) {
|
||||
Attributes = BitConverter.ToUInt32(data, 0);
|
||||
var pathNodesLength = BitConverter.ToUInt16(data, 4);
|
||||
Label = new string(BytesToUInt16s(data).Skip(3).TakeWhile(i => i != 0).Select(i => (char)i).ToArray());
|
||||
var pos = 6 + 2 * (Label.Length + 1);
|
||||
var pathNodesEnd = pos + pathNodesLength;
|
||||
DevicePathNodes = new List<DevicePathNode>();
|
||||
while (pos + 4 <= pathNodesEnd) {
|
||||
var len = BitConverter.ToUInt16(data, pos + 2);
|
||||
if (len < 4 || pos + len > pathNodesEnd) {
|
||||
return; // throw new Exception("Bad entry.");
|
||||
}
|
||||
DevicePathNodes.Add(new DevicePathNode(data.Skip(pos).Take(len).ToArray()));
|
||||
pos += len;
|
||||
}
|
||||
Arguments = data.Skip(pathNodesEnd).ToArray();
|
||||
}
|
||||
public byte[] ToBytes() {
|
||||
return new byte[0]
|
||||
.Concat(BitConverter.GetBytes((UInt32) Attributes))
|
||||
.Concat(BitConverter.GetBytes((UInt16) DevicePathNodes.Sum(n => n.Data.Length + 4)))
|
||||
.Concat(Encoding.Unicode.GetBytes(Label + "\0"))
|
||||
.Concat(DevicePathNodes.SelectMany(n => n.ToBytes()))
|
||||
.Concat(Arguments)
|
||||
.ToArray();
|
||||
}
|
||||
public DevicePathNode FileNameNode {
|
||||
get {
|
||||
var d = DevicePathNodes;
|
||||
return d.Count > 1 && d[d.Count - 1].Type == 0x7F && d[d.Count - 2].Type == 0x04 ? d[d.Count - 2] : null;
|
||||
}
|
||||
}
|
||||
public bool HasFileName {
|
||||
get {
|
||||
return FileNameNode != null;
|
||||
}
|
||||
}
|
||||
public string FileName {
|
||||
get {
|
||||
if (!HasFileName) {
|
||||
return "";
|
||||
}
|
||||
return new string(Encoding.Unicode.GetChars(FileNameNode.Data).TakeWhile(c => c != '\0').ToArray());
|
||||
}
|
||||
set {
|
||||
if (!HasFileName) {
|
||||
throw new Exception("Logic error: Setting FileName on a bad boot entry.");
|
||||
}
|
||||
FileNameNode.Data = Encoding.Unicode.GetBytes(value + "\0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GUID of the global EFI variables.
|
||||
*/
|
||||
@@ -199,7 +125,7 @@ public class Efi {
|
||||
* @param guid GUID of the EFI variable.
|
||||
* @return Information about the EFI variable.
|
||||
*/
|
||||
private static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
||||
public static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
||||
Variable result = new Variable();
|
||||
result.Name = name;
|
||||
result.Guid = guid;
|
||||
@@ -222,24 +148,21 @@ public class Efi {
|
||||
if (len == buf.Length) {
|
||||
continue;
|
||||
}
|
||||
if (len > 0 || Marshal.GetLastWin32Error() == 0) {
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
const int ERROR_ENVVAR_NOT_FOUND = 0xCB;
|
||||
if (error == ERROR_ENVVAR_NOT_FOUND) {
|
||||
return result;
|
||||
}
|
||||
if (len > 0 || error == 0) {
|
||||
result.Data = new byte[len];
|
||||
Array.Copy(buf, 0, result.Data, 0, len);
|
||||
return result;
|
||||
}
|
||||
switch (len != 0 ? 0 : Marshal.GetLastWin32Error()) {
|
||||
case 203:
|
||||
// Not found.
|
||||
return result;
|
||||
case 87:
|
||||
throw new Exception("GetVariable: Invalid parameter");
|
||||
case 1314:
|
||||
throw new Exception("GetVariable: Privilege not held");
|
||||
default:
|
||||
throw new Exception("GetVariable: error " + Marshal.GetLastWin32Error());
|
||||
const int ERROR_INVALID_FUNCTION = 0x1;
|
||||
var msg = $"GetVariable: error 0x{error:X08}: ${new Win32Exception(error).Message}";
|
||||
throw error == ERROR_INVALID_FUNCTION ? new NotImplementedException(msg) : new Exception(msg);
|
||||
}
|
||||
}
|
||||
throw new Exception("GetFirmwareEnvironmentVariable: too big data");
|
||||
throw new Exception("GetVariable: result exceeds 1MB");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,7 +171,7 @@ public class Efi {
|
||||
* @param v Information of the variable.
|
||||
* @param dryRun Don't actually set the variable.
|
||||
*/
|
||||
private static void SetVariable(Variable v, bool dryRun = false) {
|
||||
public static void SetVariable(Variable v, bool dryRun = false) {
|
||||
Setup.WriteLine($"Writing EFI variable {v.Name} (see log for details)");
|
||||
Setup.Log($"Writing EFI variable: {v}");
|
||||
if (dryRun) {
|
||||
@@ -334,169 +257,20 @@ public class Efi {
|
||||
return Enumerable.Range(0, bytes.Length / 2).Select(i => (UInt16) (bytes[2 * i] + 0x100 * bytes[2 * i + 1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the said boot entry from BootOrder.
|
||||
*
|
||||
* @param label Label of the boot entry.
|
||||
* @param fileName File name of the boot entry.
|
||||
* @param dryRun Don't actually disable the entry.
|
||||
* @return True, if the entry was found in BootOrder.
|
||||
*/
|
||||
public static bool DisableBootEntry(string label, string fileName, bool dryRun = false) {
|
||||
Variable bootOrder;
|
||||
try {
|
||||
bootOrder = GetVariable("BootOrder");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (bootOrder.Data == null) {
|
||||
return false;
|
||||
}
|
||||
Setup.Log($"Read EFI variable: {bootOrder}");
|
||||
var found = false;
|
||||
var bootOrderInts = new List<UInt16>();
|
||||
foreach (var num in BytesToUInt16s(bootOrder.Data)) {
|
||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
||||
if (entry.Data != null) {
|
||||
var entryData = new BootEntryData(entry.Data);
|
||||
if (entryData.Label == label && entryData.FileName == fileName) {
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bootOrderInts.Add(num);
|
||||
}
|
||||
if (found) {
|
||||
bootOrder.Data = bootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||
SetVariable(bootOrder, dryRun);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and enable the said boot entry from BootOrder.
|
||||
*
|
||||
* @param label Label of the boot entry.
|
||||
* @param fileName File name of the boot entry.
|
||||
* @param dryRun Don't actually create the entry.
|
||||
*/
|
||||
public static void MakeAndEnableBootEntry(string label, string fileName, bool dryRun = false) {
|
||||
Variable msEntry = null, ownEntry = null;
|
||||
UInt16 msNum = 0, ownNum = 0;
|
||||
|
||||
// Find a free entry and the MS bootloader entry.
|
||||
Variable bootOrder = null;
|
||||
try {
|
||||
bootOrder = GetVariable("BootOrder");
|
||||
} catch {
|
||||
if (dryRun) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bootOrder == null || bootOrder.Data == null) {
|
||||
throw new Exception("MakeBootEntry: Could not read BootOrder. Maybe your computer is defective.");
|
||||
}
|
||||
var bootCurrent = GetVariable("BootCurrent");
|
||||
if (bootCurrent.Data == null) {
|
||||
throw new Exception("MakeBootEntry: Could not read BootCurrent. Maybe your computer is defective.");
|
||||
}
|
||||
Setup.Log($"Read EFI variable: {bootOrder}");
|
||||
Setup.Log($"Read EFI variable: {bootCurrent}");
|
||||
var bootOrderInts = new List<UInt16>(BytesToUInt16s(bootOrder.Data));
|
||||
foreach (var num in BytesToUInt16s(bootCurrent.Data).Concat(bootOrderInts).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
||||
if (entry.Data == null) {
|
||||
// Use only Boot0000 .. Boot00FF because some firmwares expect that.
|
||||
if (ownEntry == null && num < 0x100) {
|
||||
ownNum = num;
|
||||
ownEntry = entry;
|
||||
}
|
||||
} else {
|
||||
var entryData = new BootEntryData(entry.Data);
|
||||
if (!entryData.HasFileName) {
|
||||
continue;
|
||||
}
|
||||
if (entryData.Label == label && entryData.FileName == fileName) {
|
||||
ownNum = num;
|
||||
ownEntry = entry;
|
||||
}
|
||||
if (msEntry == null && entryData.FileName.StartsWith("\\EFI\\Microsoft\\Boot\\bootmgfw.efi", StringComparison.OrdinalIgnoreCase)) {
|
||||
msNum = num;
|
||||
msEntry = entry;
|
||||
}
|
||||
}
|
||||
if (ownEntry != null && msEntry != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ownEntry == null) {
|
||||
throw new Exception("MakeBootEntry: Boot entry list is full.");
|
||||
} else if (msEntry == null) {
|
||||
throw new Exception("MakeBootEntry: Windows Boot Manager not found.");
|
||||
} else {
|
||||
Setup.Log($"Read EFI variable: {msEntry}");
|
||||
// Make a new boot entry using the MS entry as a starting point.
|
||||
var entryData = new BootEntryData(msEntry.Data);
|
||||
entryData.Arguments = Encoding.UTF8.GetBytes(label + "\0");
|
||||
entryData.Attributes = 1; // LOAD_OPTION_ACTIVE
|
||||
entryData.Label = label;
|
||||
entryData.FileName = fileName;
|
||||
ownEntry.Attributes = 7; // EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
|
||||
ownEntry.Data = entryData.ToBytes();
|
||||
SetVariable(ownEntry, dryRun);
|
||||
}
|
||||
|
||||
var msPos = bootOrderInts.IndexOf(msNum);
|
||||
var ownPos = bootOrderInts.IndexOf(ownNum);
|
||||
var mustAdd = ownPos == -1;
|
||||
var mustMove = 0 <= msPos && msPos <= ownPos;
|
||||
if (mustAdd || mustMove) {
|
||||
if (mustMove) {
|
||||
bootOrderInts.RemoveAt(ownPos);
|
||||
}
|
||||
bootOrderInts.Insert(msPos < 0 ? 0 : msPos, ownNum);
|
||||
bootOrder.Data = bootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||
SetVariable(bootOrder, dryRun);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the boot entries.
|
||||
*/
|
||||
public static void LogBootEntries() {
|
||||
try {
|
||||
var bootOrder = GetVariable("BootOrder");
|
||||
var bootCurrent = GetVariable("BootCurrent");
|
||||
Setup.Log($"LogBootOrder: {bootOrder}");
|
||||
Setup.Log($"LogBootOrder: {bootCurrent}");
|
||||
var bootOrderInts = new List<UInt16>(BytesToUInt16s(bootOrder.Data));
|
||||
// Windows can't enumerate EFI variables, and trying them all is too slow.
|
||||
// BootOrder + BootCurrent + the first 0xff entries should be enough.
|
||||
var seen = new HashSet<UInt16>();
|
||||
foreach (var num in bootOrderInts.Concat(BytesToUInt16s(bootCurrent.Data)).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
||||
if (seen.Contains(num)) {
|
||||
continue;
|
||||
}
|
||||
seen.Add(num);
|
||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
||||
if (entry.Data != null) {
|
||||
Setup.Log($"LogBootOrder: {entry}");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Setup.Log($"LogBootOrder failed: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve HackBGRT log collected during boot.
|
||||
*/
|
||||
public static string GetHackBGRTLog() {
|
||||
try {
|
||||
var log = GetVariable("HackBGRTLog", EFI_HACKBGRT_GUID);
|
||||
if (log.Data == null) {
|
||||
return "Boot log is empty.";
|
||||
}
|
||||
return new string(BytesToUInt16s(log.Data).Select(i => (char)i).ToArray());
|
||||
} catch (NotImplementedException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
return $"Log not found: {e.ToString()}";
|
||||
return $"Boot log not found: {e.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
328
src/EfiBootEntries.cs
Normal file
328
src/EfiBootEntries.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
|
||||
/**
|
||||
* A class for handling EFI boot entries. Lazy access with cache.
|
||||
* Notice: Data is not updated after the first read.
|
||||
*/
|
||||
public class EfiBootEntries {
|
||||
/**
|
||||
* Information about an EFI boot entry.
|
||||
*/
|
||||
public class BootEntryData {
|
||||
public UInt32 Attributes;
|
||||
public string Label;
|
||||
public class DevicePathNode {
|
||||
public byte Type, SubType;
|
||||
public byte[] Data;
|
||||
public DevicePathNode(byte[] data) {
|
||||
Type = data[0];
|
||||
SubType = data[1];
|
||||
Data = data.Skip(4).ToArray();
|
||||
}
|
||||
public byte[] ToBytes() {
|
||||
var len = Data.Length + 4;
|
||||
return new byte[] { Type, SubType, (byte)(len & 0xff), (byte)(len >> 8) }.Concat(Data).ToArray();
|
||||
}
|
||||
}
|
||||
public List<DevicePathNode> DevicePathNodes;
|
||||
public byte[] Arguments;
|
||||
|
||||
public BootEntryData(byte[] data) {
|
||||
Attributes = BitConverter.ToUInt32(data, 0);
|
||||
var pathNodesLength = BitConverter.ToUInt16(data, 4);
|
||||
Label = new string(Efi.BytesToUInt16s(data).Skip(3).TakeWhile(i => i != 0).Select(i => (char)i).ToArray());
|
||||
var pos = 6 + 2 * (Label.Length + 1);
|
||||
var pathNodesEnd = pos + pathNodesLength;
|
||||
DevicePathNodes = new List<DevicePathNode>();
|
||||
Arguments = new byte[0];
|
||||
while (pos + 4 <= pathNodesEnd) {
|
||||
var len = BitConverter.ToUInt16(data, pos + 2);
|
||||
if (len < 4 || pos + len > pathNodesEnd) {
|
||||
return; // throw new Exception("Bad entry.");
|
||||
}
|
||||
var node = new DevicePathNode(data.Skip(pos).Take(len).ToArray());
|
||||
DevicePathNodes.Add(node);
|
||||
if (node.Type == 0x7f && node.SubType == 0xff) {
|
||||
// End of entire device path.
|
||||
// Apparently some firmwares produce paths with unused nodes at the end.
|
||||
break;
|
||||
}
|
||||
pos += len;
|
||||
}
|
||||
Arguments = data.Skip(pathNodesEnd).ToArray();
|
||||
}
|
||||
public byte[] ToBytes() {
|
||||
return new byte[0]
|
||||
.Concat(BitConverter.GetBytes((UInt32) Attributes))
|
||||
.Concat(BitConverter.GetBytes((UInt16) DevicePathNodes.Sum(n => n.Data.Length + 4)))
|
||||
.Concat(Encoding.Unicode.GetBytes(Label + "\0"))
|
||||
.Concat(DevicePathNodes.SelectMany(n => n.ToBytes()))
|
||||
.Concat(Arguments)
|
||||
.ToArray();
|
||||
}
|
||||
public DevicePathNode FileNameNode {
|
||||
get {
|
||||
var d = DevicePathNodes;
|
||||
return d.Count > 1 && d[d.Count - 1].Type == 0x7F && d[d.Count - 2].Type == 0x04 ? d[d.Count - 2] : null;
|
||||
}
|
||||
}
|
||||
public bool HasFileName {
|
||||
get {
|
||||
return FileNameNode != null;
|
||||
}
|
||||
}
|
||||
public string FileName {
|
||||
get {
|
||||
if (!HasFileName) {
|
||||
return "";
|
||||
}
|
||||
return new string(Encoding.Unicode.GetChars(FileNameNode.Data).TakeWhile(c => c != '\0').ToArray());
|
||||
}
|
||||
set {
|
||||
if (!HasFileName) {
|
||||
throw new Exception("Logic error: Setting FileName on a bad boot entry.");
|
||||
}
|
||||
FileNameNode.Data = Encoding.Unicode.GetBytes(value + "\0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of the own boot entry.
|
||||
*/
|
||||
public enum OwnEntryStatus {
|
||||
NotFound,
|
||||
Disabled,
|
||||
EnabledAfterWindows,
|
||||
Enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to the Windows boot loader.
|
||||
*/
|
||||
public const string WindowsLoaderPath = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
|
||||
|
||||
/**
|
||||
* Path to the HackBGRT loader.
|
||||
*/
|
||||
public const string OwnLoaderPath = "\\EFI\\HackBGRT\\loader.efi";
|
||||
|
||||
private readonly Dictionary<UInt16, (Efi.Variable, BootEntryData)> cache;
|
||||
private readonly Efi.Variable BootOrder;
|
||||
private readonly Efi.Variable BootCurrent;
|
||||
private readonly List<UInt16> BootOrderInts;
|
||||
private readonly List<UInt16> BootCurrentInts;
|
||||
|
||||
/**
|
||||
* Constructor. Reads BootOrder and BootCurrent.
|
||||
*/
|
||||
public EfiBootEntries() {
|
||||
cache = new Dictionary<UInt16, (Efi.Variable, BootEntryData)>();
|
||||
BootOrder = Efi.GetVariable("BootOrder");
|
||||
BootCurrent = Efi.GetVariable("BootCurrent");
|
||||
if (BootOrder.Data == null) {
|
||||
throw new Exception("Could not read BootOrder.");
|
||||
}
|
||||
BootCurrentInts = new List<UInt16>(Efi.BytesToUInt16s(BootCurrent.Data ?? new byte[0]));
|
||||
BootOrderInts = new List<UInt16>(Efi.BytesToUInt16s(BootOrder.Data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boot entry with the given number.
|
||||
*
|
||||
* @param num Number of the boot entry.
|
||||
* @return The boot entry.
|
||||
*/
|
||||
public (Efi.Variable, BootEntryData) GetEntry(UInt16 num) {
|
||||
if (!cache.ContainsKey(num)) {
|
||||
var v = Efi.GetVariable(String.Format("Boot{0:X04}", num));
|
||||
cache[num] = (v, v.Data == null ? null : new BootEntryData(v.Data));
|
||||
}
|
||||
return cache[num];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find entry by file name.
|
||||
*
|
||||
* @param fileName File name of the boot entry.
|
||||
* @return The boot entry.
|
||||
*/
|
||||
public (UInt16, Efi.Variable, BootEntryData) FindEntry(string fileName) {
|
||||
var rest = Enumerable.Range(0, 0xff).Select(i => (UInt16) i);
|
||||
var entryAccessOrder = BootCurrentInts.Concat(BootOrderInts).Concat(rest);
|
||||
foreach (var num in entryAccessOrder) {
|
||||
var (v, e) = GetEntry(num);
|
||||
if (fileName == null ? e == null : (e != null && e.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase))) {
|
||||
return (num, v, e);
|
||||
}
|
||||
}
|
||||
return (0xffff, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Windows boot entry.
|
||||
*/
|
||||
public (UInt16, Efi.Variable, BootEntryData) WindowsEntry {
|
||||
get { return FindEntry(WindowsLoaderPath); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HackBGRT boot entry.
|
||||
*/
|
||||
public (UInt16, Efi.Variable, BootEntryData) OwnEntry {
|
||||
get { return FindEntry(OwnLoaderPath); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a free boot entry.
|
||||
*/
|
||||
public (UInt16, Efi.Variable, BootEntryData) FreeEntry {
|
||||
get { return FindEntry(null); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the own entry is enabled.
|
||||
*/
|
||||
public OwnEntryStatus GetOwnEntryStatus() {
|
||||
var (ownNum, ownVar, _) = OwnEntry;
|
||||
if (ownVar == null) {
|
||||
return OwnEntryStatus.NotFound;
|
||||
}
|
||||
var (msNum, _, _) = WindowsEntry;
|
||||
var msPos = BootOrderInts.IndexOf(msNum);
|
||||
var ownPos = BootOrderInts.IndexOf(ownNum);
|
||||
if (ownPos < 0) {
|
||||
return OwnEntryStatus.Disabled;
|
||||
}
|
||||
if (ownPos < msPos || msPos < 0) {
|
||||
return OwnEntryStatus.Enabled;
|
||||
}
|
||||
return OwnEntryStatus.EnabledAfterWindows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the said boot entry from BootOrder.
|
||||
*
|
||||
* @param dryRun Don't actually write to NVRAM.
|
||||
* @return True, if the entry was found in BootOrder.
|
||||
*/
|
||||
public bool DisableOwnEntry(bool dryRun = false) {
|
||||
var (ownNum, ownVar, _) = OwnEntry;
|
||||
if (ownVar == null) {
|
||||
Setup.Log("Own entry not found.");
|
||||
return false;
|
||||
}
|
||||
Setup.Log($"Old boot order: {BootOrder}");
|
||||
if (!BootOrderInts.Contains(ownNum)) {
|
||||
Setup.Log("Own entry not in BootOrder.");
|
||||
} else {
|
||||
Setup.Log($"Disabling own entry: {ownNum:X04}");
|
||||
BootOrderInts.Remove(ownNum);
|
||||
BootOrder.Data = BootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||
Efi.SetVariable(BootOrder, dryRun);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the boot entry.
|
||||
*
|
||||
* @param alwaysCopyFromMS If true, do not preserve any existing data.
|
||||
* @param dryRun Don't actually write to NVRAM.
|
||||
*/
|
||||
public void MakeOwnEntry(bool alwaysCopyFromMS, bool dryRun = false) {
|
||||
var (msNum, msVar, msEntry) = WindowsEntry;
|
||||
var (ownNum, ownVar, ownEntry) = OwnEntry;
|
||||
if (ownVar == null) {
|
||||
(ownNum, ownVar, ownEntry) = FreeEntry;
|
||||
if (ownVar == null) {
|
||||
throw new Exception("MakeOwnEntry: No free entry.");
|
||||
}
|
||||
Setup.Log($"Creating own entry {ownNum:X4}.");
|
||||
} else {
|
||||
Setup.Log($"Updating own entry {ownNum:X4}.");
|
||||
}
|
||||
|
||||
Setup.Log($"Read EFI variable: {msVar}");
|
||||
Setup.Log($"Read EFI variable: {ownVar}");
|
||||
// Make a new boot entry using the MS entry as a starting point.
|
||||
if (!alwaysCopyFromMS && ownEntry != null) {
|
||||
// Shim expects the arguments to be a filename or nothing.
|
||||
// But BCDEdit expects some Microsoft-specific data.
|
||||
// Modify the entry so that BCDEdit still recognises it
|
||||
// but the data becomes a valid UCS-2 / UTF-16LE file name.
|
||||
var str = new string(ownEntry.Arguments.Take(12).Select(c => (char) c).ToArray());
|
||||
if (str == "WINDOWS\0\x01\0\0\0") {
|
||||
ownEntry.Arguments[8] = (byte) 'X';
|
||||
} else if (str != "WINDOWS\0\x58\0\0\0") {
|
||||
// Not recognized. Clear the arguments.
|
||||
ownEntry.Arguments = new byte[0];
|
||||
}
|
||||
} else {
|
||||
if (msEntry == null) {
|
||||
throw new Exception("MakeOwnEntry: Windows Boot Manager not found.");
|
||||
}
|
||||
ownEntry = msEntry;
|
||||
ownEntry.Arguments = new byte[0];
|
||||
ownEntry.Label = "HackBGRT";
|
||||
ownEntry.FileName = OwnLoaderPath;
|
||||
}
|
||||
ownEntry.Attributes = 1; // LOAD_OPTION_ACTIVE
|
||||
ownVar.Attributes = 7; // EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
|
||||
ownVar.Data = ownEntry.ToBytes();
|
||||
Efi.SetVariable(ownVar, dryRun);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the own boot entry.
|
||||
*
|
||||
* @param dryRun Don't actually write to NVRAM.
|
||||
*/
|
||||
public void EnableOwnEntry(bool dryRun = false) {
|
||||
var (ownNum, ownVar, _) = OwnEntry;
|
||||
if (ownVar == null) {
|
||||
Setup.Log("Own entry not found.");
|
||||
return;
|
||||
}
|
||||
var (msNum, _, _) = WindowsEntry;
|
||||
var msPos = BootOrderInts.IndexOf(msNum);
|
||||
var ownPos = BootOrderInts.IndexOf(ownNum);
|
||||
var mustAdd = ownPos == -1;
|
||||
var mustMove = 0 <= msPos && msPos <= ownPos;
|
||||
Setup.Log($"Old boot order: {BootOrder}");
|
||||
if (mustAdd || mustMove) {
|
||||
Setup.Log($"Enabling own entry: {ownNum:X04}");
|
||||
if (mustMove) {
|
||||
BootOrderInts.RemoveAt(ownPos);
|
||||
}
|
||||
BootOrderInts.Insert(msPos < 0 ? 0 : msPos, ownNum);
|
||||
BootOrder.Data = BootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||
Efi.SetVariable(BootOrder, dryRun);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the boot entries.
|
||||
*/
|
||||
public void LogEntries() {
|
||||
Setup.Log($"LogEntries: {BootOrder}");
|
||||
Setup.Log($"LogEntries: {BootCurrent}");
|
||||
// Windows can't enumerate EFI variables, and trying them all is too slow.
|
||||
// BootOrder + BootCurrent + the first 0xff entries should be enough.
|
||||
var seen = new HashSet<UInt16>();
|
||||
foreach (var num in BootOrderInts.Concat(BootCurrentInts).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
||||
if (seen.Contains(num)) {
|
||||
continue;
|
||||
}
|
||||
seen.Add(num);
|
||||
var (v, e) = GetEntry(num);
|
||||
if (e != null) {
|
||||
Setup.Log($"LogEntries: {v}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/Esp.cs
67
src/Esp.cs
@@ -19,6 +19,22 @@ public sealed class Esp {
|
||||
}
|
||||
}
|
||||
|
||||
/** Output of mountvol. */
|
||||
private static string MountvolOutput;
|
||||
|
||||
/** Possible drive letter for the ESP. */
|
||||
public static string DriveLetters = "ABZYXWVUTSRQPONMLKJIHGFEDC";
|
||||
|
||||
/** Does MountvolOutput contain /S? */
|
||||
public static bool MountvolESPNotSupported {
|
||||
get {
|
||||
if (MountvolOutput == null) {
|
||||
MountvolOutput = Setup.Execute("mountvol", "", false);
|
||||
}
|
||||
return MountvolOutput.Contains(" /S") == false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor: do nothing.
|
||||
*/
|
||||
@@ -67,29 +83,48 @@ public sealed class Esp {
|
||||
*
|
||||
* @return true if the drive was found.
|
||||
*/
|
||||
public static bool Find() {
|
||||
if (MountInstance != null) {
|
||||
return true;
|
||||
public static bool FindOrMount() {
|
||||
return Location != null || FindWithMountvol() || Mount() || TryAllDrives();
|
||||
}
|
||||
Setup.Log("Esp.Find()");
|
||||
|
||||
/**
|
||||
* Find the EFI System Partition, if it's already mounted.
|
||||
*
|
||||
* @return true if the drive was found.
|
||||
*/
|
||||
private static bool FindWithMountvol() {
|
||||
Setup.Log("Esp: Detect from mountvol output.");
|
||||
try {
|
||||
// Match "The EFI System Partition is mounted at E:\" with some language support.
|
||||
MountvolOutput = Setup.Execute("mountvol", "", false);
|
||||
var re = new Regex(" EFI[^\n]*(?:\n[ \t]*)?([A-Z]:\\\\)");
|
||||
var m = re.Match(Setup.Execute("mountvol", "", false));
|
||||
var m = re.Match(MountvolOutput);
|
||||
if (m.Success && TryPath(m.Groups[1].Captures[0].Value)) {
|
||||
return true;
|
||||
}
|
||||
Setup.Log("Esp.Find: no match");
|
||||
Setup.Log("Esp: no match");
|
||||
} catch (Exception e) {
|
||||
Setup.Log($"Esp.Find: {e.ToString()}");
|
||||
Setup.Log($"Esp: {e.ToString()}");
|
||||
}
|
||||
for (char c = 'A'; c <= 'Z'; ++c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try all drive letters to find the EFI System Partition.
|
||||
*
|
||||
* @return true if the drive was found.
|
||||
*/
|
||||
private static bool TryAllDrives() {
|
||||
Setup.Log("Esp: Detect by trying all drive letters.");
|
||||
foreach (char c in DriveLetters) {
|
||||
if (TryPath(c + ":\\")) {
|
||||
Setup.Log($"Esp.Find: found {c}");
|
||||
Setup.Log($"Esp: found {c}");
|
||||
if (c == 'C') {
|
||||
Setup.Log("Esp: WARNING: It's unlikely that C: is really the ESP.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Setup.Log("Esp.Find: not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,11 +133,15 @@ public sealed class Esp {
|
||||
*
|
||||
* @return true if the drive was mounted, false otherwise.
|
||||
*/
|
||||
public static bool Mount() {
|
||||
if (MountInstance != null) {
|
||||
return true;
|
||||
private static bool Mount() {
|
||||
if (MountvolESPNotSupported) {
|
||||
return false;
|
||||
}
|
||||
Setup.Log("Esp: Try to mount with mountvol.");
|
||||
foreach (char c in DriveLetters) {
|
||||
if (Directory.Exists(c + ":\\")) {
|
||||
continue;
|
||||
}
|
||||
for (char c = 'A'; c <= 'Z'; ++c) {
|
||||
Setup.Log($"Esp.Mount: {c}");
|
||||
if (Setup.Execute("mountvol", c + ": /S", true) != null) {
|
||||
MountInstance = new Esp();
|
||||
|
||||
307
src/Setup.cs
307
src/Setup.cs
@@ -13,8 +13,12 @@ using System.Runtime.CompilerServices;
|
||||
using System.Management;
|
||||
using Microsoft.Win32;
|
||||
|
||||
[assembly: AssemblyInformationalVersionAttribute(GIT_DESCRIBE.data)]
|
||||
[assembly: AssemblyProductAttribute("HackBGRT")]
|
||||
#if GIT_DESCRIBE
|
||||
[assembly: AssemblyVersion(GIT_DESCRIBE.numeric)]
|
||||
#endif
|
||||
[assembly: AssemblyProduct("HackBGRT")]
|
||||
[assembly: AssemblyTitle("HackBGRT Installer")]
|
||||
[assembly: AssemblyDescription("HackBGRT boot logo changer for UEFI")]
|
||||
|
||||
/**
|
||||
* HackBGRT Setup.
|
||||
@@ -59,12 +63,8 @@ public class Setup {
|
||||
}
|
||||
|
||||
/** @var The privileged actions. */
|
||||
protected static readonly string[] privilegedActions = new string[] {
|
||||
protected static readonly string[] PrivilegedActions = new string[] {
|
||||
"install",
|
||||
"allow-secure-boot",
|
||||
"allow-bitlocker",
|
||||
"allow-bad-loader",
|
||||
"skip-shim",
|
||||
"enable-entry", "disable-entry",
|
||||
"enable-bcdedit", "disable-bcdedit",
|
||||
"enable-overwrite", "disable-overwrite",
|
||||
@@ -74,6 +74,16 @@ public class Setup {
|
||||
"show-boot-log",
|
||||
};
|
||||
|
||||
/** @var Forwardable arguments. */
|
||||
protected static readonly string[] ForwardableArguments = new string[] {
|
||||
"dry-run",
|
||||
"batch",
|
||||
"skip-shim",
|
||||
"allow-secure-boot",
|
||||
"allow-bitlocker",
|
||||
"allow-bad-loader",
|
||||
};
|
||||
|
||||
/** @var The target directory. */
|
||||
protected string InstallPath;
|
||||
|
||||
@@ -96,9 +106,24 @@ public class Setup {
|
||||
/** @var Dry run? */
|
||||
protected bool DryRun;
|
||||
|
||||
/** @var User-defined ESP path */
|
||||
protected string UserEspPath;
|
||||
|
||||
/** @var Run in batch mode? */
|
||||
protected bool Batch;
|
||||
|
||||
/** @var Skip the shim? */
|
||||
protected bool SkipShim;
|
||||
|
||||
/** @var Allow Secure Boot to be enabled? */
|
||||
protected bool AllowSecureBoot;
|
||||
|
||||
/** @var Allow BitLocker to be enabled? */
|
||||
protected bool AllowBitLocker;
|
||||
|
||||
/** @var Allow bad loader in config file? */
|
||||
protected bool AllowBadLoader;
|
||||
|
||||
/** @var Is the loader signed? */
|
||||
protected bool LoaderIsSigned = false;
|
||||
|
||||
@@ -144,10 +169,9 @@ public class Setup {
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
* @param nullOnFail If set, return null if the program exits with a code other than 0, even if there was some output.
|
||||
* @return The output, or null if the execution failed.
|
||||
* @return The output and exit code.
|
||||
*/
|
||||
public static string Execute(string app, string args, bool nullOnFail) {
|
||||
public static (string Output, int ExitCode) Execute(string app, string args) {
|
||||
Log($"Execute: {app} {args}");
|
||||
try {
|
||||
var info = new ProcessStartInfo(app, args);
|
||||
@@ -157,13 +181,26 @@ public class Setup {
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit();
|
||||
Log($"Exit code: {p.ExitCode}, output:\n{output}\n\n");
|
||||
return (nullOnFail && p.ExitCode != 0) ? null : output;
|
||||
return (output, p.ExitCode);
|
||||
} catch (Exception e) {
|
||||
Log($"Execute failed: {e.ToString()}");
|
||||
return null;
|
||||
return (null, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute another process, return the output.
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
* @param nullOnFail If set, return null if the program exits with a code other than 0, even if there was some output.
|
||||
* @return The output, or null if the execution failed.
|
||||
*/
|
||||
public static string Execute(string app, string args, bool nullOnFail) {
|
||||
var result = Execute(app, args);
|
||||
return (nullOnFail && result.ExitCode != 0) ? null : result.Output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for required privileges (access to ESP and EFI vars).
|
||||
*
|
||||
@@ -239,6 +276,8 @@ public class Setup {
|
||||
0x0200 => "ia64",
|
||||
0x8664 => "x64",
|
||||
0xaa64 => "aa64",
|
||||
0x01c2 => "arm",
|
||||
0x01c4 => "arm",
|
||||
_ => $"unknown-{peArch:x4}"
|
||||
};
|
||||
} catch {
|
||||
@@ -253,9 +292,17 @@ public class Setup {
|
||||
if (DryRun) {
|
||||
Directory.CreateDirectory(Path.Combine("dry-run", "EFI"));
|
||||
Esp.TryPath("dry-run", false);
|
||||
} else if (UserEspPath != null) {
|
||||
WriteLine($"Using user-defined ESP path: {UserEspPath}");
|
||||
if (!Esp.TryPath(UserEspPath, false)) {
|
||||
throw new SetupException("The ESP path doesn't look like an EFI System Partition.");
|
||||
}
|
||||
if (Esp.Location == null && !Esp.Find() && !Esp.Mount() && !Batch) {
|
||||
}
|
||||
if (!Esp.FindOrMount() && !Batch) {
|
||||
WriteLine("EFI System Partition was not found.");
|
||||
if (Esp.MountvolESPNotSupported) {
|
||||
WriteLine("Your computer doesn't support mountvol /S. You have to mount ESP manually.");
|
||||
}
|
||||
WriteLine("Press enter to exit, or give ESP path here: ");
|
||||
string s = Console.ReadLine();
|
||||
Log($"User input: {s}");
|
||||
@@ -270,6 +317,9 @@ public class Setup {
|
||||
throw new SetupException("EFI System Partition was not found.");
|
||||
}
|
||||
WriteLine($"EFI System Partition location is {Esp.Location}");
|
||||
if (Esp.Location.StartsWith("C:")) {
|
||||
WriteLine("Warning: EFI System Partition is not normally C: drive.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,20 +357,22 @@ public class Setup {
|
||||
g.DrawImageUnscaledAndClipped(img, new Rectangle(Point.Empty, img.Size));
|
||||
}
|
||||
try {
|
||||
bmp.Save(newName, ImageFormat.Bmp);
|
||||
} catch {
|
||||
var ms = new MemoryStream();
|
||||
bmp.Save(ms, ImageFormat.Bmp);
|
||||
var bytes = ms.ToArray();
|
||||
File.WriteAllBytes(newName, bytes);
|
||||
} catch (Exception e) {
|
||||
Log($"InstallImageFile failed: {e.ToString()}");
|
||||
throw new SetupException($"Failed to install image {name} to {newName}.");
|
||||
}
|
||||
}
|
||||
WriteLine($"Installed image {name} to {newName}.");
|
||||
WriteLine($"Installed image {name} to {newName}, size {img.Width}x{img.Height}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install files to ESP.
|
||||
*
|
||||
* @param skipShim Whether to skip installing shim.
|
||||
*/
|
||||
protected void InstallFiles(bool skipShim) {
|
||||
protected void InstallFiles() {
|
||||
var loaderSource = Path.Combine("efi-signed", $"boot{EfiArch}.efi");
|
||||
LoaderIsSigned = true;
|
||||
if (!File.Exists(loaderSource)) {
|
||||
@@ -332,7 +384,7 @@ public class Setup {
|
||||
}
|
||||
var shimSource = Path.Combine("shim-signed", $"shim{EfiArch}.efi");
|
||||
var mmSource = Path.Combine("shim-signed", $"mm{EfiArch}.efi");
|
||||
if (!skipShim) {
|
||||
if (!SkipShim) {
|
||||
if (!File.Exists(shimSource)) {
|
||||
throw new SetupException($"Missing shim ({shimSource}), can't install shim for {EfiArch}!");
|
||||
}
|
||||
@@ -352,16 +404,23 @@ public class Setup {
|
||||
var lines = File.ReadAllLines("config.txt");
|
||||
Log($"config.txt:\n{String.Join("\n", lines)}");
|
||||
foreach (var line in lines.Where(s => s.StartsWith("image="))) {
|
||||
var delim = "path=\\EFI\\HackBGRT\\";
|
||||
var delim = "path=";
|
||||
var i = line.IndexOf(delim);
|
||||
if (i > 0) {
|
||||
var dir = "\\EFI\\HackBGRT\\";
|
||||
if (line.Substring(i + delim.Length).StartsWith(dir)) {
|
||||
InstallImageFile(line.Substring(i + delim.Length + dir.Length));
|
||||
}
|
||||
if (!line.Substring(i + delim.Length).StartsWith("\\")) {
|
||||
InstallImageFile(line.Substring(i + delim.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
var loaderDest = "loader.efi";
|
||||
if (!skipShim) {
|
||||
if (!SkipShim) {
|
||||
InstallFile(shimSource, loaderDest);
|
||||
InstallFile(mmSource, $"mm{EfiArch}.efi");
|
||||
InstallFile(loaderSource, "\u4957\u444e\u574f\u0053\u0058"); // bytes "WINDOWS\0X\0" as UTF-16
|
||||
loaderDest = $"grub{EfiArch}.efi";
|
||||
}
|
||||
InstallFile(loaderSource, loaderDest);
|
||||
@@ -372,7 +431,7 @@ public class Setup {
|
||||
|
||||
var enrollHashPath = $"EFI\\HackBGRT\\{loaderDest}";
|
||||
var enrollKeyPath = "EFI\\HackBGRT\\certificate.cer";
|
||||
if (skipShim) {
|
||||
if (SkipShim) {
|
||||
if (LoaderIsSigned) {
|
||||
WriteLine($"Remember to enroll {enrollKeyPath} in your firmware!");
|
||||
} else {
|
||||
@@ -390,18 +449,44 @@ public class Setup {
|
||||
* Enable HackBGRT with bcdedit.
|
||||
*/
|
||||
protected void EnableBCDEdit() {
|
||||
if (DryRun) {
|
||||
WriteLine("Dry run, skip enabling with BCDEdit.");
|
||||
return;
|
||||
}
|
||||
var bcdeditEnum = Execute("bcdedit", "/enum firmware");
|
||||
if (bcdeditEnum.ExitCode != 0) {
|
||||
if (bcdeditEnum.Output != null) {
|
||||
var lines = bcdeditEnum.Output.Split("\n".ToCharArray());
|
||||
var lastLine = lines.Where(s => s != "").Last();
|
||||
WriteLine($"BCDEdit failed with: {lastLine}");
|
||||
}
|
||||
WriteLine("BCDEdit failed. See README for further information.");
|
||||
throw new SetupException("Failed to enable HackBGRT with BCDEdit!");
|
||||
}
|
||||
try {
|
||||
var re = new Regex("[{][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]");
|
||||
var guid = re.Match(Execute("bcdedit", "/copy {bootmgr} /d HackBGRT", true)).Value;
|
||||
var bcdCopy = Execute("bcdedit", "/copy {bootmgr} /d HackBGRT").Output;
|
||||
if (bcdCopy == null) {
|
||||
throw new SetupException("Failed to create a new BCDEdit entry for HackBGRT!");
|
||||
}
|
||||
var match = re.Match(bcdCopy);
|
||||
if (!match.Success) {
|
||||
throw new SetupException("Failed to get a GUID for the new BCDEdit entry for HackBGRT!");
|
||||
}
|
||||
var guid = match.Value;
|
||||
Execute("bcdedit", $"/set {guid} device partition={Esp.Location}", true);
|
||||
Execute("bcdedit", $"/set {guid} path \\EFI\\HackBGRT\\loader.efi", true);
|
||||
Execute("bcdedit", $"/set {guid} path {EfiBootEntries.OwnLoaderPath}", true);
|
||||
foreach (var arg in new string[] { "locale", "inherit", "default", "resumeobject", "displayorder", "toolsdisplayorder", "timeout" }) {
|
||||
Execute("bcdedit", $"/deletevalue {guid} {arg}", true);
|
||||
}
|
||||
var fwbootmgr = "{fwbootmgr}";
|
||||
Execute("bcdedit", $"/set {fwbootmgr} displayorder {guid} /addfirst", true);
|
||||
// Verify that the entry was created.
|
||||
Execute("bcdedit", "/enum firmware", true);
|
||||
WriteLine("Enabled NVRAM entry for HackBGRT with BCDEdit.");
|
||||
|
||||
Execute("bcdedit", $"/enum {guid}", true);
|
||||
var e = new EfiBootEntries();
|
||||
e.MakeOwnEntry(false, DryRun); // Fix load options for shim.
|
||||
e.EnableOwnEntry(DryRun);
|
||||
Execute("bcdedit", $"/enum {guid}", true);
|
||||
} catch (Exception e) {
|
||||
Log($"EnableBCDEdit failed: {e.ToString()}");
|
||||
@@ -427,40 +512,76 @@ public class Setup {
|
||||
} else if (entry.IndexOf("HackBGRT") >= 0) {
|
||||
found = true;
|
||||
Log($"Disabling HackBGRT entry {guid}.");
|
||||
if (Execute("bcdedit", $"/delete {guid}", true) == null) {
|
||||
if (!DryRun && Execute("bcdedit", $"/delete {guid}", true) == null) {
|
||||
Log($"DisableBCDEdit failed to delete {guid}.");
|
||||
}
|
||||
} else {
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
if (found && !disabled) {
|
||||
}
|
||||
if (found) {
|
||||
if (!disabled) {
|
||||
throw new SetupException("Failed to disable HackBGRT with BCDEdit!");
|
||||
}
|
||||
WriteLine("Disabled NVRAM entry for HackBGRT with BCDEdit.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT boot entry.
|
||||
*/
|
||||
protected void EnableEntry() {
|
||||
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", DryRun);
|
||||
var e = new EfiBootEntries();
|
||||
e.MakeOwnEntry(true, DryRun);
|
||||
e.EnableOwnEntry(DryRun);
|
||||
WriteLine("Enabled NVRAM entry for HackBGRT.");
|
||||
// Verify that the entry was created.
|
||||
Efi.LogBootEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HackBGRT boot entry.
|
||||
*/
|
||||
protected void DisableEntry() {
|
||||
Efi.DisableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", DryRun);
|
||||
new EfiBootEntries().DisableOwnEntry(DryRun);
|
||||
WriteLine("Disabled NVRAM entry for HackBGRT.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Log boot entries and report the status of HackBGRT boot entry.
|
||||
*/
|
||||
public void CheckEntries() {
|
||||
try {
|
||||
var efiBootEntries = new EfiBootEntries();
|
||||
efiBootEntries.LogEntries();
|
||||
var entryStatusText = efiBootEntries.GetOwnEntryStatus() switch {
|
||||
EfiBootEntries.OwnEntryStatus.NotFound => "missing",
|
||||
EfiBootEntries.OwnEntryStatus.Disabled => "disabled (not in BootOrder)",
|
||||
EfiBootEntries.OwnEntryStatus.EnabledAfterWindows => "disabled (after Windows in BootOrder)",
|
||||
EfiBootEntries.OwnEntryStatus.Enabled => "enabled correctly",
|
||||
_ => throw new SetupException("Unknown HackBGRT boot entry status!")
|
||||
};
|
||||
WriteLine($"HackBGRT boot entry is {entryStatusText}.");
|
||||
} catch (Exception e) {
|
||||
WriteLine($"Failed to check EFI boot entries: {e.Message}");
|
||||
Setup.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paths related to MS boot loader.
|
||||
*/
|
||||
protected (string Ms, string MsDir, string MsGrub, string MsMokManager) GetMsLoaderPaths() {
|
||||
var ms = Esp.MsLoaderPath;
|
||||
var msDir = Path.GetDirectoryName(ms);
|
||||
var msGrub = Path.Combine(msDir, $"grub{EfiArch}.efi");
|
||||
var msMm = Path.Combine(msDir, $"mm{EfiArch}.efi");
|
||||
return (ms, msDir, msGrub, msMm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT by overwriting the MS boot loader.
|
||||
*/
|
||||
protected void OverwriteMsLoader() {
|
||||
var ms = Esp.MsLoaderPath;
|
||||
var (ms, msDir, msGrub, msMm) = GetMsLoaderPaths();
|
||||
var backup = BackupLoaderPath;
|
||||
|
||||
if (DetectLoader(ms) == BootLoaderType.Microsoft) {
|
||||
@@ -470,13 +591,16 @@ public class Setup {
|
||||
// Duplicate check, but better to be sure...
|
||||
throw new SetupException("Missing MS boot loader backup!");
|
||||
}
|
||||
var msDir = Path.GetDirectoryName(ms);
|
||||
var msGrub = Path.Combine(msDir, $"grub{EfiArch}.efi");
|
||||
var msMm = Path.Combine(msDir, $"mm{EfiArch}.efi");
|
||||
var loader = Path.Combine(InstallPath, "loader.efi");
|
||||
if (SkipShim == (DetectLoader(loader) == BootLoaderType.Shim)) {
|
||||
throw new SetupException("Bad skip-shim usage. Install and enable with consistent options.");
|
||||
}
|
||||
try {
|
||||
InstallFile(Path.Combine(InstallPath, "loader.efi"), ms, false);
|
||||
InstallFile(loader, ms, false);
|
||||
if (!SkipShim) {
|
||||
InstallFile(Path.Combine(InstallPath, $"grub{EfiArch}.efi"), msGrub, false);
|
||||
InstallFile(Path.Combine(InstallPath, $"mm{EfiArch}.efi"), msMm, false);
|
||||
}
|
||||
} catch (SetupException e) {
|
||||
WriteLine(e.Message);
|
||||
if (DetectLoader(ms) != BootLoaderType.Microsoft) {
|
||||
@@ -487,6 +611,7 @@ public class Setup {
|
||||
throw new SetupException("Rollback failed, your system may be unbootable! Create a rescue disk IMMEADIATELY!");
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,11 +619,13 @@ public class Setup {
|
||||
* Restore the MS boot loader if it was previously replaced.
|
||||
*/
|
||||
protected void RestoreMsLoader() {
|
||||
var ms = Esp.MsLoaderPath;
|
||||
var (ms, msDir, msGrub, msMm) = GetMsLoaderPaths();
|
||||
if (DetectLoader(ms) == BootLoaderType.Own || DetectLoader(ms) == BootLoaderType.Shim) {
|
||||
WriteLine("Disabling an old version of HackBGRT.");
|
||||
InstallFile(BackupLoaderPath, ms, false);
|
||||
WriteLine($"{ms} has been restored.");
|
||||
File.Delete(msGrub);
|
||||
File.Delete(msMm);
|
||||
}
|
||||
if (DetectLoader(BackupLoaderPath) == BootLoaderType.Own) {
|
||||
File.Delete(BackupLoaderPath);
|
||||
@@ -511,7 +638,7 @@ public class Setup {
|
||||
*/
|
||||
protected void VerifyLoaderConfig() {
|
||||
var lines = File.ReadAllLines("config.txt");
|
||||
var loader = lines.Where(s => s.StartsWith("boot=")).Select(s => s.Substring(5)).Prepend("").Last();
|
||||
var loader = lines.Where(s => s.StartsWith("boot=")).Select(s => s.Substring(5)).LastOrDefault();
|
||||
if (loader == null) {
|
||||
throw new SetupException("config.txt does not contain a boot=... line!");
|
||||
}
|
||||
@@ -575,7 +702,9 @@ public class Setup {
|
||||
protected void Uninstall() {
|
||||
Disable();
|
||||
try {
|
||||
if (Directory.Exists(InstallPath)) {
|
||||
Directory.Delete(InstallPath, true);
|
||||
}
|
||||
WriteLine($"HackBGRT has been removed from {InstallPath}.");
|
||||
} catch (Exception e) {
|
||||
Log($"Uninstall failed: {e.ToString()}");
|
||||
@@ -585,10 +714,8 @@ public class Setup {
|
||||
|
||||
/**
|
||||
* Check Secure Boot status and inform the user.
|
||||
*
|
||||
* @param allowSecureBoot Allow Secure Boot to be enabled?
|
||||
*/
|
||||
protected void HandleSecureBoot(bool allowSecureBoot) {
|
||||
protected void HandleSecureBoot() {
|
||||
int secureBoot = Efi.GetSecureBootStatus();
|
||||
if (secureBoot == 0) {
|
||||
WriteLine("Secure Boot is disabled, good!");
|
||||
@@ -604,7 +731,7 @@ public class Setup {
|
||||
}
|
||||
WriteLine("Otherwise your machine may become unbootable.");
|
||||
if (Batch) {
|
||||
if (allowSecureBoot) {
|
||||
if (AllowSecureBoot) {
|
||||
return;
|
||||
}
|
||||
throw new SetupException("Aborting because of Secure Boot.");
|
||||
@@ -628,10 +755,8 @@ public class Setup {
|
||||
|
||||
/**
|
||||
* Check BitLocker status and inform the user.
|
||||
*
|
||||
* @param allowBitLocker Allow BitLocker to be enabled?
|
||||
*/
|
||||
protected void HandleBitLocker(bool allowBitLocker) {
|
||||
protected void HandleBitLocker() {
|
||||
var output = Execute("manage-bde", "-status", true);
|
||||
if (output == null) {
|
||||
WriteLine("BitLocker status could not be determined.");
|
||||
@@ -651,7 +776,7 @@ public class Setup {
|
||||
WriteLine("BitLocker status is unclear. Run manage-bde -status to check.");
|
||||
}
|
||||
if (Batch) {
|
||||
if (allowBitLocker) {
|
||||
if (AllowBitLocker) {
|
||||
return;
|
||||
}
|
||||
throw new SetupException("Aborting because of BitLocker.");
|
||||
@@ -708,8 +833,13 @@ public class Setup {
|
||||
* @param arch The architecture.
|
||||
*/
|
||||
protected void SetArch(string arch) {
|
||||
var detectedArch = DetectArchFromOS();
|
||||
if (arch == "") {
|
||||
var detectedArch = Environment.Is64BitOperatingSystem ? "x64" : "ia32";
|
||||
try {
|
||||
detectedArch = DetectArchFromOS();
|
||||
} catch {
|
||||
WriteLine($"Failed to detect OS architecture, assuming {detectedArch}.");
|
||||
}
|
||||
if (arch == "" || arch == null) {
|
||||
EfiArch = detectedArch;
|
||||
WriteLine($"Your OS uses arch={EfiArch}. This will be checked again during installation.");
|
||||
} else {
|
||||
@@ -781,7 +911,7 @@ public class Setup {
|
||||
WriteLine(" I = install");
|
||||
WriteLine(" - creates a new EFI boot entry for HackBGRT");
|
||||
WriteLine(" J = install (alternative)");
|
||||
WriteLine(" - creates a new EFI boot entry with an alternative method (BCDEdit)");
|
||||
WriteLine(" - creates a new EFI boot entry with an alternative method");
|
||||
WriteLine(" - try this if the first option doesn't work");
|
||||
WriteLine(" O = install (legacy)");
|
||||
WriteLine(" - overwrites the MS boot loader; gets removed by Windows updates");
|
||||
@@ -801,17 +931,17 @@ public class Setup {
|
||||
var k = Console.ReadKey().Key;
|
||||
Log($"User input: {k}");
|
||||
WriteLine();
|
||||
if (k == ConsoleKey.I || k == ConsoleKey.O || k == ConsoleKey.F) {
|
||||
if (k == ConsoleKey.I || k == ConsoleKey.J || k == ConsoleKey.O || k == ConsoleKey.F) {
|
||||
Configure();
|
||||
}
|
||||
if (k == ConsoleKey.I) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-entry" });
|
||||
RunPrivilegedActions(new string[] { "disable", "install", "enable-bcdedit" });
|
||||
} else if (k == ConsoleKey.J) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-bcdedit" });
|
||||
RunPrivilegedActions(new string[] { "disable", "install", "enable-entry" });
|
||||
} else if (k == ConsoleKey.O) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-overwrite" });
|
||||
RunPrivilegedActions(new string[] { "disable", "install", "enable-overwrite" });
|
||||
} else if (k == ConsoleKey.F) {
|
||||
RunPrivilegedActions(new string[] { "install" });
|
||||
RunPrivilegedActions(new string[] { "disable-overwrite", "install" });
|
||||
} else if (k == ConsoleKey.D) {
|
||||
RunPrivilegedActions(new string[] { "disable" });
|
||||
} else if (k == ConsoleKey.R) {
|
||||
@@ -853,10 +983,19 @@ public class Setup {
|
||||
|
||||
InitEspPath();
|
||||
InitEspInfo();
|
||||
var bootLog = $"\n--- BOOT LOG START ---\n{Efi.GetHackBGRTLog()}\n--- BOOT LOG END ---";
|
||||
Func<string> tryGetBootLog = () => {
|
||||
try {
|
||||
return $"\n--- BOOT LOG START ---\n{Efi.GetHackBGRTLog()}\n--- BOOT LOG END ---";
|
||||
} catch (NotImplementedException e) {
|
||||
throw new SetupException($"Looks like you're not booting in UEFI mode. ({e.Message})");
|
||||
}
|
||||
};
|
||||
var bootLog = tryGetBootLog();
|
||||
Setup.Log(bootLog);
|
||||
|
||||
Efi.LogBGRT();
|
||||
Efi.LogBootEntries();
|
||||
CheckEntries();
|
||||
|
||||
if (GetBootTime() is DateTime bootTime) {
|
||||
var configTime = new[] { File.GetCreationTime("config.txt"), File.GetLastWriteTime("config.txt") }.Max();
|
||||
Log($"Boot time = {bootTime}, config.txt changed = {configTime}");
|
||||
@@ -867,44 +1006,34 @@ public class Setup {
|
||||
if (IsHiberbootEnabled()) {
|
||||
WriteLine("You may have to disable Fast Startup (Hiberboot) to reboot properly.");
|
||||
}
|
||||
bool allowSecureBoot = false;
|
||||
bool allowBitLocker = false;
|
||||
bool allowBadLoader = false;
|
||||
bool skipShim = false;
|
||||
Action<Action> verify = (Action revert) => {
|
||||
try {
|
||||
VerifyLoaderConfig();
|
||||
} catch (SetupException e) {
|
||||
if (allowBadLoader) {
|
||||
if (AllowBadLoader) {
|
||||
WriteLine($"Warning: {e.Message}");
|
||||
} else {
|
||||
WriteLine($"Error: {e.Message}");
|
||||
WriteLine($"Reverting. Use batch mode with allow-bad-loader to override.");
|
||||
revert();
|
||||
throw new SetupException("Check your configuration and try again.");
|
||||
throw new SetupException($"Error in configuration: {e.Message}");
|
||||
}
|
||||
}
|
||||
};
|
||||
Action<Action, Action> enable = (Action enable, Action revert) => {
|
||||
if (skipShim) {
|
||||
HandleSecureBoot(allowSecureBoot);
|
||||
if (SkipShim) {
|
||||
HandleSecureBoot();
|
||||
}
|
||||
HandleBitLocker(allowBitLocker);
|
||||
HandleBitLocker();
|
||||
enable();
|
||||
verify(revert);
|
||||
CheckEntries();
|
||||
Execute("bcdedit", "/enum firmware", true);
|
||||
};
|
||||
foreach (var arg in actions) {
|
||||
Log($"Running action '{arg}'.");
|
||||
if (arg == "install") {
|
||||
InstallFiles(skipShim);
|
||||
} else if (arg == "allow-secure-boot") {
|
||||
allowSecureBoot = true;
|
||||
} else if (arg == "allow-bitlocker") {
|
||||
allowBitLocker = true;
|
||||
} else if (arg == "allow-bad-loader") {
|
||||
allowBadLoader = true;
|
||||
} else if (arg == "skip-shim") {
|
||||
skipShim = true;
|
||||
InstallFiles();
|
||||
} else if (arg == "enable-entry") {
|
||||
enable(() => EnableEntry(), () => DisableEntry());
|
||||
} else if (arg == "disable-entry") {
|
||||
@@ -925,6 +1054,7 @@ public class Setup {
|
||||
BootToFW();
|
||||
} else if (arg == "show-boot-log") {
|
||||
WriteLine(bootLog);
|
||||
CheckEntries();
|
||||
} else {
|
||||
throw new SetupException($"Invalid action: '{arg}'!");
|
||||
}
|
||||
@@ -941,6 +1071,7 @@ public class Setup {
|
||||
var self = Assembly.GetExecutingAssembly().Location;
|
||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(self));
|
||||
WriteLine($"HackBGRT installer version: {Version}");
|
||||
Log($"Windows version: {Environment.OSVersion}");
|
||||
Log($"Args: {String.Join(" ", args)}");
|
||||
Environment.ExitCode = new Setup().Run(args);
|
||||
}
|
||||
@@ -953,14 +1084,24 @@ public class Setup {
|
||||
protected int Run(string[] args) {
|
||||
DryRun = args.Contains("dry-run");
|
||||
Batch = args.Contains("batch");
|
||||
ForwardArguments = String.Join(" ", args.Where(s => s == "dry-run" || s == "batch" || s.StartsWith("arch=")));
|
||||
AllowBadLoader = args.Contains("allow-bad-loader");
|
||||
AllowSecureBoot = args.Contains("allow-secure-boot");
|
||||
AllowBitLocker = args.Contains("allow-bitlocker");
|
||||
SkipShim = args.Contains("skip-shim");
|
||||
ForwardArguments = String.Join(" ", args.Where(s => ForwardableArguments.Contains(s) || s.StartsWith("arch=") || s.StartsWith("esp=")));
|
||||
try {
|
||||
SetArch(args.Prepend("arch=").Last(s => s.StartsWith("arch=")).Substring(5));
|
||||
if (!(Directory.Exists("efi") || Directory.Exists("efi-signed")) || !File.Exists("config.txt")) {
|
||||
WriteLine("This setup program is not in the correct directory!");
|
||||
WriteLine("Please extract the zip file and run the setup program from there.");
|
||||
return 1;
|
||||
}
|
||||
SetArch(args.Where(s => s.StartsWith("arch=")).Select(s => s.Substring(5)).LastOrDefault());
|
||||
UserEspPath = args.Where(s => s.StartsWith("esp=")).Select(s => s.Substring(4)).LastOrDefault();
|
||||
if (args.Contains("is-elevated") && !HasPrivileges() && !DryRun) {
|
||||
WriteLine("This installer needs to be run as administrator!");
|
||||
return 1;
|
||||
}
|
||||
var actions = args.Where(s => privilegedActions.Contains(s));
|
||||
var actions = args.Where(s => PrivilegedActions.Contains(s));
|
||||
if (actions.Count() > 0) {
|
||||
RunPrivilegedActions(actions);
|
||||
return 0;
|
||||
@@ -983,7 +1124,13 @@ public class Setup {
|
||||
WriteLine();
|
||||
WriteLine($"Unexpected error: {e.Message}");
|
||||
Log(e.ToString());
|
||||
if (e is MissingMemberException || e is TypeLoadException) {
|
||||
WriteLine("This installer requires a recent version of .Net Framework.");
|
||||
WriteLine("Use Windows Update or download manually:");
|
||||
WriteLine("https://dotnet.microsoft.com/en-us/download/dotnet-framework");
|
||||
} else {
|
||||
WriteLine("If this is the most current release, please report this bug.");
|
||||
}
|
||||
return 1;
|
||||
} finally {
|
||||
if (DryRun) {
|
||||
|
||||
10
src/config.c
10
src/config.c
@@ -1,10 +1,10 @@
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE base_dir, const CHAR16* path) {
|
||||
void* data = 0;
|
||||
UINTN data_bytes = 0;
|
||||
data = LoadFileWithPadding(root_dir, path, &data_bytes, 4);
|
||||
data = LoadFileWithPadding(base_dir, path, &data_bytes, 4);
|
||||
if (!data) {
|
||||
Log(1, L"Failed to load configuration (%s)!\n", path);
|
||||
return FALSE;
|
||||
@@ -61,7 +61,7 @@ BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir,
|
||||
str[j] = 0;
|
||||
++j;
|
||||
}
|
||||
ReadConfigLine(config, root_dir, &str[i]);
|
||||
ReadConfigLine(config, base_dir, &str[i]);
|
||||
i = j;
|
||||
}
|
||||
// NOTICE: string is not freed, because paths are not copied.
|
||||
@@ -129,7 +129,7 @@ static void ReadConfigResolution(struct HackBGRT_config* config, const CHAR16* l
|
||||
}
|
||||
}
|
||||
|
||||
void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* line) {
|
||||
void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE base_dir, const CHAR16* line) {
|
||||
line = TrimLeft(line);
|
||||
if (line[0] == L'#' || line[0] == 0) {
|
||||
return;
|
||||
@@ -152,7 +152,7 @@ void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, co
|
||||
return;
|
||||
}
|
||||
if (StrnCmp(line, L"config=", 7) == 0) {
|
||||
ReadConfigFile(config, root_dir, line + 7);
|
||||
ReadConfigFile(config, base_dir, line + 7);
|
||||
return;
|
||||
}
|
||||
if (StrnCmp(line, L"resolution=", 11) == 0) {
|
||||
|
||||
@@ -39,17 +39,17 @@ struct HackBGRT_config {
|
||||
* Read a configuration parameter. (May recursively read config files.)
|
||||
*
|
||||
* @param config The configuration to modify.
|
||||
* @param root_dir The root directory, in case the parameter contains an include.
|
||||
* @param base_dir The base directory, in case the parameter contains an include.
|
||||
* @param line The configuration line to parse.
|
||||
*/
|
||||
extern void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* line);
|
||||
extern void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE base_dir, const CHAR16* line);
|
||||
|
||||
/**
|
||||
* Read a configuration file. (May recursively read more files.)
|
||||
*
|
||||
* @param config The configuration to modify.
|
||||
* @param root_dir The root directory.
|
||||
* @param base_dir The base directory.
|
||||
* @param path The path to the file.
|
||||
* @return FALSE, if the file couldn't be read, TRUE otherwise.
|
||||
*/
|
||||
extern BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* path);
|
||||
extern BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE base_dir, const CHAR16* path);
|
||||
|
||||
27
src/efi.c
27
src/efi.c
@@ -64,6 +64,33 @@ EFI_DEVICE_PATH *FileDevicePath(IN EFI_HANDLE Device OPTIONAL, IN CHAR16 *FileNa
|
||||
return new_path;
|
||||
}
|
||||
|
||||
CHAR16 *DevicePathToStr(EFI_DEVICE_PATH *DevPath) {
|
||||
UINTN path_length = 0;
|
||||
for (EFI_DEVICE_PATH *p0 = DevPath;; p0 = NextDevicePathNode(p0)) {
|
||||
if (DevicePathType(p0) != MEDIA_DEVICE_PATH || DevicePathSubType(p0) != MEDIA_FILEPATH_DP) {
|
||||
break;
|
||||
}
|
||||
path_length += DevicePathNodeLength(p0) + 1;
|
||||
}
|
||||
|
||||
CHAR16* str;
|
||||
UINTN size_str = (path_length + 1) * sizeof(*str);
|
||||
|
||||
if (!path_length || EFI_ERROR(BS->AllocatePool(EfiBootServicesData, size_str, (void**)&str))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
UINTN pos = 0;
|
||||
for (EFI_DEVICE_PATH *p0 = DevPath; pos < path_length; p0 = NextDevicePathNode(p0)) {
|
||||
FILEPATH_DEVICE_PATH *f = (FILEPATH_DEVICE_PATH *) p0;
|
||||
BS->CopyMem(str + pos, f->PathName, StrLen(f->PathName) * sizeof(*str));
|
||||
pos += DevicePathNodeLength(p0) + 1;
|
||||
str[pos - 1] = L'\\';
|
||||
}
|
||||
str[pos - 1] = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
INTN CompareMem(IN CONST VOID *Dest, IN CONST VOID *Src, IN UINTN len) {
|
||||
CONST UINT8 *d = Dest, *s = Src;
|
||||
for (UINTN i = 0; i < len; ++i) {
|
||||
|
||||
55
src/main.c
55
src/main.c
@@ -239,17 +239,17 @@ static BMP* MakeBMP(int w, int h, UINT8 r, UINT8 g, UINT8 b) {
|
||||
/**
|
||||
* Load a bitmap or generate a black one.
|
||||
*
|
||||
* @param root_dir The root directory for loading a BMP.
|
||||
* @param path The BMP path within the root directory; NULL for a black BMP.
|
||||
* @param base_dir The directory for loading a BMP.
|
||||
* @param path The BMP path within the directory; NULL for a black BMP.
|
||||
* @return The loaded BMP, or 0 if not available.
|
||||
*/
|
||||
static BMP* LoadBMP(EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
static BMP* LoadBMP(EFI_FILE_HANDLE base_dir, const CHAR16* path) {
|
||||
if (!path) {
|
||||
return MakeBMP(1, 1, 0, 0, 0); // empty path = black image
|
||||
}
|
||||
Log(config.debug, L"Loading %s.\n", path);
|
||||
UINTN size = 0;
|
||||
BMP* bmp = LoadFile(root_dir, path, &size);
|
||||
BMP* bmp = LoadFile(base_dir, path, &size);
|
||||
if (bmp) {
|
||||
if (size >= bmp->file_size
|
||||
&& CompareMem(bmp, "BM", 2) == 0
|
||||
@@ -299,9 +299,9 @@ static void CropBMP(BMP* bmp, int w, int h) {
|
||||
/**
|
||||
* The main logic for BGRT modification.
|
||||
*
|
||||
* @param root_dir The root directory for loading a BMP.
|
||||
* @param base_dir The directory for loading a BMP.
|
||||
*/
|
||||
void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
void HackBgrt(EFI_FILE_HANDLE base_dir) {
|
||||
// REMOVE: simply delete all BGRT entries.
|
||||
if (config.action == HackBGRT_REMOVE) {
|
||||
HandleAcpiTables(config.action, 0);
|
||||
@@ -352,7 +352,7 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
// Get the image (either old or new).
|
||||
BMP* new_bmp = old_bmp;
|
||||
if (config.action == HackBGRT_REPLACE) {
|
||||
new_bmp = LoadBMP(root_dir, config.image_path);
|
||||
new_bmp = LoadBMP(base_dir, config.image_path);
|
||||
}
|
||||
|
||||
// No image = no need for BGRT.
|
||||
@@ -415,6 +415,10 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
BS = ST_->BootServices;
|
||||
RT = ST_->RuntimeServices;
|
||||
|
||||
// Clear the screen to wipe the vendor logo.
|
||||
ST->ConOut->EnableCursor(ST->ConOut, 0);
|
||||
ST->ConOut->ClearScreen(ST->ConOut);
|
||||
|
||||
Log(0, L"HackBGRT version: %s\n", version);
|
||||
|
||||
EFI_LOADED_IMAGE* image;
|
||||
@@ -435,10 +439,39 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
CHAR16* default_dir_path = L"\\EFI\\HackBGRT";
|
||||
Log(config.debug, L"Default directory: %s\n", default_dir_path);
|
||||
EFI_FILE_HANDLE default_dir;
|
||||
if (EFI_ERROR(root_dir->Open(root_dir, &default_dir, default_dir_path, EFI_FILE_MODE_READ, 0))) {
|
||||
Log(config.debug, L"Failed to open HackBGRT default directory.\n");
|
||||
default_dir = root_dir;
|
||||
}
|
||||
|
||||
CHAR16* working_dir_path = DevicePathToStr(image->FilePath);
|
||||
for (int i = StrLen(working_dir_path), skipped_last_component = 0; i--;) {
|
||||
if (working_dir_path[i] == L'/' || working_dir_path[i] == L'\\') {
|
||||
working_dir_path[i] = skipped_last_component++ ? L'\\' : L'\0';
|
||||
}
|
||||
}
|
||||
Log(config.debug, L"Working directory: %s\n", working_dir_path);
|
||||
EFI_FILE_HANDLE working_dir;
|
||||
if (EFI_ERROR(root_dir->Open(root_dir, &working_dir, working_dir_path, EFI_FILE_MODE_READ, 0))) {
|
||||
Log(config.debug, L"Failed to open HackBGRT working directory.\n");
|
||||
working_dir = default_dir;
|
||||
}
|
||||
|
||||
EFI_FILE_HANDLE base_dir = working_dir;
|
||||
|
||||
EFI_SHELL_PARAMETERS_PROTOCOL *shell_param_proto = NULL;
|
||||
if (EFI_ERROR(BS->OpenProtocol(image_handle, TmpGuidPtr((EFI_GUID) EFI_SHELL_PARAMETERS_PROTOCOL_GUID), (void**) &shell_param_proto, 0, 0, EFI_OPEN_PROTOCOL_GET_PROTOCOL)) || shell_param_proto->Argc <= 1) {
|
||||
const CHAR16* config_path = L"\\EFI\\HackBGRT\\config.txt";
|
||||
if (!ReadConfigFile(&config, root_dir, config_path)) {
|
||||
const CHAR16* config_path = L"config.txt";
|
||||
retry_read_config:
|
||||
if (!ReadConfigFile(&config, base_dir, config_path)) {
|
||||
if (base_dir != default_dir && StrCmp(default_dir_path, working_dir_path) != 0) {
|
||||
base_dir = default_dir;
|
||||
Log(config.debug, L"Trying the default directory.\n");
|
||||
goto retry_read_config;
|
||||
}
|
||||
Log(1, L"No config, no command line!\n", config_path);
|
||||
goto fail;
|
||||
}
|
||||
@@ -446,7 +479,7 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
CHAR16 **argv = shell_param_proto->Argv;
|
||||
int argc = shell_param_proto->Argc;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
ReadConfigLine(&config, root_dir, argv[i]);
|
||||
ReadConfigLine(&config, base_dir, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +488,7 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
}
|
||||
|
||||
SetResolution(config.resolution_x, config.resolution_y);
|
||||
HackBgrt(root_dir);
|
||||
HackBgrt(base_dir);
|
||||
|
||||
EFI_HANDLE next_image_handle = 0;
|
||||
static CHAR16 backup_boot_path[] = L"\\EFI\\HackBGRT\\bootmgfw-original.efi";
|
||||
|
||||
@@ -177,6 +177,7 @@ EFI_STATUS WaitKey(UINT64 timeout_ms) {
|
||||
|
||||
EFI_INPUT_KEY ReadKey(UINT64 timeout_ms) {
|
||||
EFI_INPUT_KEY key = {0};
|
||||
ST->ConOut->EnableCursor(ST->ConOut, 1);
|
||||
WaitKey(timeout_ms);
|
||||
ST->ConIn->ReadKeyStroke(ST->ConIn, &key);
|
||||
return key;
|
||||
|
||||
Reference in New Issue
Block a user