49 Commits

Author SHA1 Message Date
Lauri Kenttä
d40ce9f6c5 Make batch example the same as option I 2025-07-29 11:24:47 +03:00
Lauri Kenttä
447ff30f61 Fix getting last line from BCDEdit output 2025-07-29 11:14:37 +03:00
Lauri Kenttä
5e9ef85451 Update change log and tag v2.5.2 2025-04-13 00:06:42 +03:00
Lauri Kenttä
96599b1838 Try to load config.txt from the current dir
To allow multiple configurations for HackBGRT, or to allow a custom
installation path, try to load config.txt from the same directory as
the executable.
2025-04-12 23:59:35 +03:00
Lauri Kenttä
611ab30db3 Improve output and README for BootOrder problems
Report own entry status (missing, disabled, after windows, enabled).
Also improve README instructions on the topic.
2025-04-12 23:59:35 +03:00
Lauri Kenttä
ffa3e335ea Improve output and README for BCDEdit errors 2025-04-12 23:59:35 +03:00
Lauri Kenttä
a1f6297759 Report if UEFI is missing (can't read variables) 2025-04-12 23:59:35 +03:00
Lauri Kenttä
afd0780b61 Try to avoid mistaking C: for ESP 2025-04-12 23:59:35 +03:00
Lauri Kenttä
320e154457 Check for missing mountvol /S 2025-04-12 23:59:35 +03:00
Lauri Kenttä
785307c0e2 Allow overriding ESP path 2025-04-12 23:59:35 +03:00
Lauri Kenttä
be62caa400 Add missing README entries for dry-run and arch 2025-04-12 23:59:35 +03:00
Lauri Kenttä
734ea21308 Don't require Windows Boot Manager entry if it's not needed 2025-04-12 23:59:35 +03:00
Lauri Kenttä
1946765680 Log Windows version 2025-04-12 23:59:35 +03:00
Lauri Kenttä
79ee253108 Update change log and tag v2.5.1 2024-08-18 09:38:43 +03:00
Lauri Kenttä
82abb0c120 Update shim to 15.8
Use RockyLinux shim, they have all variants (x86_64, aa64, ia32).
After Windows update KB5041585, the old shim SBAT is not accepted.

Fixes #197.
2024-08-18 09:37:01 +03:00
Lauri Kenttä
830db410ea Properly propagate configuration error message 2024-07-20 15:26:18 +03:00
Lauri Kenttä
1e36d7e388 Support compiling with MSYS2 tools in Windows
Windows and MSYS2 need csc parameters to start with "-", not "/".
Now setup.exe can be compiled with the csc in MSYS2 mono package.
The EFI binaries can already be compiled with MSYS2 clang & lld.
2024-06-22 19:38:29 +03:00
Lauri Kenttä
9038e20cd2 Update change log and tag v2.5.0 2024-06-21 15:36:46 +03:00
Lauri Kenttä
9a0d4737e1 Improve setup.exe metadata 2024-06-21 15:36:46 +03:00
Lauri Kenttä
fa6fae3aa3 Catch errors in writing image 2024-06-13 15:50:19 +03:00
Lauri Kenttä
90fb8e47c1 Don't panic if BootCurrent is not found 2024-06-13 15:50:19 +03:00
Lauri Kenttä
c826149183 Refactor EFI boot entry code into a new class 2024-06-13 15:50:19 +03:00
Lauri Kenttä
af4f99aab6 If missing files, warn that zip is not extracted 2024-05-09 19:50:56 +03:00
Lauri Kenttä
8a97382a2e Clarify 'Log is empty', add a section in README 2024-05-09 19:50:51 +03:00
Lauri Kenttä
8e6466990a Skip the workaround in a44b9290 if skipping shim 2024-04-20 21:58:01 +03:00
Lauri Kenttä
6f94f6bc28 Properly handle skip-shim with enable-overwrite 2024-04-20 21:57:47 +03:00
Lauri Kenttä
bc600a6c2f Handle more command-line options before executing actions 2024-04-20 21:31:19 +03:00
Lauri Kenttä
022ea9b93b Log image dimensions when installing 2024-04-20 21:31:19 +03:00
Lauri Kenttä
7d7d4c2aa4 Clarify installation and upgrading in README 2024-04-20 16:04:54 +03:00
Lauri Kenttä
f1c8b11d6b Add some troubleshooting info to README 2024-04-20 15:54:20 +03:00
Lauri Kenttä
a0553856f0 Fix batch installation instructions 2024-04-20 15:49:33 +03:00
Lauri Kenttä
ffa29f6ffc Update gnu-efi to 3.0.18 2024-04-11 18:07:40 +03:00
Lauri Kenttä
b9e23c91a3 Update change log and tag 2.4.1 2024-04-11 17:48:55 +03:00
Lauri Kenttä
697c57355b Disable old version before copying files
Apparently some people still have the obsolete v1.5.1 installed
and manage to break things by copying v2.x config in place.
2024-04-11 17:46:13 +03:00
Lauri Kenttä
da16365508 Report if BCDEdit is not working
One possible cause for BCDEdit failure is Windows error 0x800703EE.
"The volume for a file has been externally altered so that the opened file is no longer valid."
Suggested solutions include disabling antivirus or backup software.
2024-04-11 17:46:13 +03:00
Lauri Kenttä
665a4732ca Detect device paths which contain extra data
Apparently some firmware may add extra data to the device path,
so exit the loop on the first end-of-path record.
Fixes GH issue #181.
2024-03-24 17:27:02 +02:00
Lauri Kenttä
39596aadfc Don't try to remove non-existent dir. 2024-01-20 12:43:00 +02:00
Lauri Kenttä
1a5b1df064 Update change log and tag 2.4.0 2023-12-31 18:35:35 +02:00
Lauri Kenttä
ea70f3ce79 Disable before enabling, and default to BCDEdit 2023-12-31 18:29:48 +02:00
Lauri Kenttä
a44b929012 Fix a shim error caused by bad load options data
Shim expects a filename or nothing in the load options.

To avoid an error message during boot, do several things:

When creating the NVRAM entry, use empty load options. The current
string ("HackBGRT\0") was just a decoration, and it's luckily ignored
by shim because the length is odd.

When creating the entry with BCDEdit, manually fix the load options.
The load options in BCDEdit entries start with "WINDOWS\0" followed
by UINT32 version, as seen in ReactOS struct BL_WINDOWS_LOAD_OPTIONS.
The version is 1, but BCDEdit seems to be happy with a higher number.
By setting this version to 'X' (0x58), the string becomes a valid
UCS-2 file name. Update the installer so that the HackBGRT loader is
installed with this weird file name.

The reason why the load options cannot be deleted completely is that
BCDEdit will recreate the entry on next boot if it doesn't find the
entry it just tried to create.

See: https://github.com/rhboot/shim/pull/621
See: https://github.com/reactos/reactos/blob/v0.4.7/boot/environ/include/bl.h#L911
2023-12-31 18:20:27 +02:00
Lauri Kenttä
9948e5a306 Fix BCDEdit dry run and add more logging 2023-12-31 17:50:14 +02:00
Lauri Kenttä
518d7c8a97 Show proper message if boot log is empty 2023-12-20 18:29:34 +02:00
Lauri Kenttä
c6108ffd62 Warn about old .Net version if methods are missing 2023-12-20 18:29:25 +02:00
Lauri Kenttä
6dc447a8ce Try to avoid some .Net Framework 4.8 features 2023-12-20 18:29:25 +02:00
Lauri Kenttä
5ec17a49e8 Detect ARM arch from MS loader 2023-12-20 18:29:25 +02:00
Lauri Kenttä
7b7309a255 Explain why the original logo is still visible 2023-12-20 18:29:25 +02:00
Lauri Kenttä
a82646a822 Wipe the vendor logo as soon as possible
Loading the image might take some minimal time. Optimize experience.
Hide cursor as well but restore it in ReadKey.
2023-12-20 18:27:19 +02:00
Lauri Kenttä
294da9c069 Get rid of \EFI\HackBGRT\ in config.txt 2023-12-16 15:13:05 +02:00
Lauri Kenttä
4096002eb2 Update issue templates 2023-12-16 15:03:05 +02:00
22 changed files with 895 additions and 442 deletions

21
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

Submodule gnu-efi updated: 965f557ab7...74bd9b60ba

View File

@@ -1,46 +1,17 @@
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
Files: debian/po/cs.po
Copyright: 2018 Michal Simunek <michal.simunek@gmail.com>
License: BSD-2-Clause
Files: debian/po/de.po
Copyright: 2017, 2018 Markus Hiereth <markus.hiereth@freenet.de>
License: BSD-2-Clause
Files: debian/po/fr.po
Copyright: 2017, 2018 Alban Vidal <alban.vidal@zordhak.fr>
License: BSD-2-Clause
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
@@ -53,3 +24,7 @@ License: BSD-2-Clause
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.

View File

@@ -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
View 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}");
}
}
}
}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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;