40 Commits

Author SHA1 Message Date
Lauri Kenttä 62cc4a8cb7 Update change log and tag v2.6.0 2026-04-02 22:37:51 +03:00
Lauri Kenttä 630adfc50b Update shim to 16.1 (x64, aa64)
Use OL10 shim.
For ia32 we're stuck with shim 15.8, distros don't support it anymore.
2026-04-02 22:36:48 +03:00
Lauri Kenttä a78b6b22a3 Only install the weirdly named loader if shim 15 is used 2026-04-02 22:32:34 +03:00
Lauri Kenttä 7db9c2d3ec Suggest booting to FW to enable HackBGRT 2026-04-02 22:32:34 +03:00
Lauri Kenttä 28129c23fb Rename setup.log to setup-log.txt for easier use 2026-04-02 22:32:34 +03:00
Lauri Kenttä bc2a192506 Improve README for TPM, Secure Boot, only one HDD 2026-04-02 22:32:34 +03:00
Lauri Kenttä 3d5efa2117 Fix GetVariable with >4kB data 2026-04-02 22:32:34 +03:00
Lauri Kenttä 683ee49110 Allow position as a fraction of resolution
UEFI recommends y=.382 (1 - 1 / (golden ratio)), make this the default.
Fractional position starts with a decimal point: x=.5 y=.382
Pixel offset relative to the center is set as before: x=123 y=456
If the coordinates were omitted, old default was: x=0 y=0
2026-03-30 19:14:31 +03:00
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
21 changed files with 938 additions and 500 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ Explain exactly the steps you did:
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.
Run the setup again and select menu option `L` (or add parameter `show-boot-log` on command line). Attach the `setup-log.txt` to this report.
+32
View File
@@ -2,6 +2,38 @@
All notable changes to this project will be documented in this file.
## 2.6.0 - 2026-04-02
### Added
- Allow setting image position as a fraction of resolution.
### Changed
- Update *shim* to 16.1 for x86_64 and Aarch64.
- Rename `setup.log` to `setup-log.txt` for easier use.
## 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
+18 -4
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
+84 -17
View File
@@ -12,35 +12,48 @@ When booting on a UEFI-based computer, Windows may show a vendor-defined logo wh
**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.
* Make sure that your computer is booting with UEFI.
* Make sure that you have read the Secure Boot instructions.
* Make sure that BitLocker is disabled, or find your recovery key.
### Secure Boot instructions
### Secure Boot
HackBGRT is not approved by Microsoft. Instead, HackBGRT comes with the *shim* boot loader, which allows to manually select HackBGRT as a trusted program. After installing HackBGRT and rebooting your computer, you have to **follow the instructions in [shim.md](shim.md)** to achieve this. These steps cannot be automated, that's the whole point of Secure Boot. Although HackBGRT is self-signed with a certificate, it's not advisable to enroll foreign certificates directly into your firmware.
The *shim* boot loader is maintained by Red Hat, Inc, and the included signed copy of *shim* is extracted from Debian GNU/Linux many thanks to the maintainers! For copyright information, see [shim-signed/COPYRIGHT](shim-signed/COPYRIGHT).
The *shim* boot loader is maintained by Red Hat, Inc, and the included signed copies of *shim* are extracted from various Linux distributions many thanks to the maintainers! For copyright information, see [shim-signed/COPYRIGHT](shim-signed/COPYRIGHT).
### TPM
TPM, or Trusted Platform Module, watches how your computer boots. When there is a change in the boot process such as using HackBGRT some things may stop working. This includes:
* BitLocker or similar disk encryption.
* Anti-cheat software.
* Windows PIN unlock method.
* Other security-related things which mention TPM.
You should disable these features before using HackBGRT. Some of them can be re-enabled afterwards, some can't. For any TPM problems, it's recommended to either uninstall HackBGRT or stop using the problematic feature.
(If you find an easy way to reconfigure the TPM, please document, test and share it. Technically one solution would be to install HackBGRT before Windows and make sure that Windows is always booted through HackBGRT. But that's easier said than done.)
### Windows installation
* Make sure you **read the above sections**.
* Get the latest release from the Releases page.
* Make sure you have only one bootable hard drive. Otherwise the automatic setup may fail.
* 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.
@@ -50,7 +63,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
@@ -66,9 +82,11 @@ 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`.
The configuration options are described in `config.txt`, which the installer copies into `[EFI System Partition]\EFI\HackBGRT\config.txt`. For debugging purposes, the same options may be given also as command line parameters in the EFI Shell.
## Images
@@ -78,9 +96,58 @@ Advanced users may edit the `config.txt` to define multiple images, in which cas
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.
## Recovery
## Troubleshooting
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.
### BCDEdit failed
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.txt` 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
+10 -9
View File
@@ -1,5 +1,5 @@
# vim: set fileencoding=utf-8
# The same options may be given also as command line parameters in the EFI Shell, which is useful for debugging.
# Lines starting with '#' are comments.
# Boot loader path. MS = either backup or original Windows boot loader.
boot=MS
@@ -8,12 +8,13 @@ boot=MS
# Multiple image lines may be present, in which case one will be picked by random.
# The image line may contain the following parts:
# Any of the following:
# - "n=(number)", a weight for this image in the randomization process. Default: n=1.
# - "x=(number)" or "x=keep", the x offset from the center. Default: x=0.
# - "y=(number)" or "y=keep", the y offset from the center. Default: y=0.
# - "o=(0|90|180|270|keep)", the screen orientation, degrees anticlockwise. Default: o=keep.
# - "x=(number) y=(number)" positions the image relative to the screen center.
# - "x=.(decimal) y=.(decimal)" positions the image as a fraction of the resolution.
# - "o=(0|90|180|270|keep)", the screen orientation, degrees anticlockwise.
# - "n=(number)", a weight for this image in the randomization process.
# - Default values: o=keep x=.5 y=.382 n=1
# One of the following:
# - "keep" to keep the firmware logo. Also keeps coordinates by default.
# - "keep" to keep the firmware logo (and coordinates, unless specified).
# - "remove" to remove the BGRT. Makes x and y meaningless.
# - "black" to use only a black image. Makes x and y meaningless.
# - "path=file.bmp" to read an image file.
@@ -21,9 +22,9 @@ boot=MS
# Examples:
# - image=remove
# - image=black
# - image= x=0 y=-200 path=topimage.bmp
# - image= x=0 y=.0 path=topimage.bmp
# - image= n=1 o=90 path=sideways.bmp
# - image= n=50 y=999999 o=keep path=probable.bmp
# - image= n=50 y=999999 path=probable.bmp
# The above examples together would produce
# - 1/54 chance for the default OS logo
# - 1/54 chance for black screen
@@ -31,7 +32,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=splash.bmp
image= path=splash.bmp
# Preferred resolution. Use 0x0 for maximum and -1x-1 for original.
resolution=0x0
+1 -1
Submodule gnu-efi updated: 965f557ab7...74bd9b60ba
+25 -50
View File
@@ -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.
+21 -270
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;
/**
@@ -34,6 +35,9 @@ public class Efi {
[DllImport("kernel32.dll", SetLastError = true)]
private static extern UInt32 GetSystemFirmwareTable(UInt32 provider, UInt32 table, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, UInt32 len);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void SetLastError(UInt32 errorCode);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct TokPriv1Luid {
public int Count;
@@ -64,88 +68,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>();
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");
}
}
}
/**
* GUID of the global EFI variables.
*/
@@ -206,7 +128,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;
@@ -225,28 +147,26 @@ public class Efi {
for (UInt32 i = 4096; i <= 1024*1024; i *= 2) {
byte[] buf = new byte[i];
SetLastError(0);
UInt32 len = GetFirmwareEnvironmentVariableEx(name, guid, buf, (UInt32) buf.Length, out result.Attributes);
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");
}
/**
@@ -255,7 +175,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) {
@@ -341,177 +261,6 @@ 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 alwaysCopyFromMS If true, do not preserve any existing data.
* @param dryRun Don't actually create the entry.
*/
public static void MakeAndEnableBootEntry(string label, string fileName, bool alwaysCopyFromMS, 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}");
Setup.Log($"Read EFI variable: {ownEntry}");
// Make a new boot entry using the MS entry as a starting point.
var entryData = new BootEntryData(msEntry.Data);
if (!alwaysCopyFromMS && ownEntry.Data != null) {
entryData = new BootEntryData(ownEntry.Data);
// 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(entryData.Arguments.Take(12).Select(c => (char) c).ToArray());
if (str == "WINDOWS\0\x01\0\0\0") {
entryData.Arguments[8] = (byte) 'X';
} else if (str != "WINDOWS\0\x58\0\0\0") {
// Not recognized. Clear the arguments.
entryData.Arguments = new byte[0];
}
} else {
entryData.Arguments = new byte[0];
entryData.Label = label;
entryData.FileName = fileName;
}
entryData.Attributes = 1; // LOAD_OPTION_ACTIVE
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.
*/
@@ -519,11 +268,13 @@ public class Efi {
try {
var log = GetVariable("HackBGRTLog", EFI_HACKBGRT_GUID);
if (log.Data == null) {
return "(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
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}");
}
}
}
}
+54 -15
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;
}
Setup.Log("Esp.Find()");
public static bool FindOrMount() {
return Location != null || FindWithMountvol() || Mount() || TryAllDrives();
}
/**
* 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;
}
for (char c = 'A'; c <= 'Z'; ++c) {
Setup.Log("Esp: Try to mount with mountvol.");
foreach (char c in DriveLetters) {
if (Directory.Exists(c + ":\\")) {
continue;
}
Setup.Log($"Esp.Mount: {c}");
if (Setup.Execute("mountvol", c + ": /S", true) != null) {
MountInstance = new Esp();
+184 -74
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,21 +63,28 @@ 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",
"disable",
"uninstall",
"boot-to-fw",
"ask-to-boot-to-fw",
"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 +107,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;
@@ -117,7 +143,7 @@ public class Setup {
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
var prefix = $"{timestamp} | pid {pid} | ";
File.AppendAllText("setup.log", prefix + s.Replace("\n", "\n" + prefix) + "\n");
File.AppendAllText("setup-log.txt", prefix + s.Replace("\n", "\n" + prefix) + "\n");
}
/**
@@ -267,9 +293,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}");
@@ -284,6 +318,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.");
}
}
/**
@@ -321,20 +358,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)) {
@@ -346,7 +385,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}!");
}
@@ -379,13 +418,17 @@ public class Setup {
}
}
var loaderDest = "loader.efi";
if (!skipShim) {
if (!SkipShim) {
InstallFile(shimSource, loaderDest);
InstallFile(mmSource, $"mm{EfiArch}.efi");
var shimContents = File.ReadAllBytes(shimSource);
var shimContentsStr = System.Text.Encoding.ASCII.GetString(shimContents);
if (shimContentsStr.IndexOf("Version: 15.") >= 0) {
InstallFile(loaderSource, "\u4957\u444e\u574f\u0053\u0058"); // bytes "WINDOWS\0X\0" as UTF-16
}
loaderDest = $"grub{EfiArch}.efi";
}
InstallFile(loaderSource, loaderDest);
InstallFile(loaderSource, "\u4957\u444e\u574f\u0053\u0058"); // bytes "WINDOWS\0X\0" as UTF-16
if (LoaderIsSigned) {
InstallFile("certificate.cer");
}
@@ -393,7 +436,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 {
@@ -417,12 +460,12 @@ public class Setup {
}
var bcdeditEnum = Execute("bcdedit", "/enum firmware");
if (bcdeditEnum.ExitCode != 0) {
WriteLine("BCDEdit is not working. Fix it or try another method.");
if (bcdeditEnum.Output != null) {
var lastLine = bcdeditEnum.Output.Split("\n".ToCharArray()).Last();
WriteLine($"BCDEdit output: {lastLine}");
WriteLine("Disable antivirus, check your hard disk, or search 'how to fix 0x800703EE'.");
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 {
@@ -437,18 +480,19 @@ public class Setup {
}
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);
WriteLine("Enabled NVRAM entry for HackBGRT with BCDEdit.");
// Verify that the entry was created.
Execute("bcdedit", "/enum firmware", true);
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", false, DryRun);
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);
Efi.LogBootEntries();
} catch (Exception e) {
Log($"EnableBCDEdit failed: {e.ToString()}");
throw new SetupException("Failed to enable HackBGRT with BCDEdit!");
@@ -492,26 +536,57 @@ public class Setup {
* Enable HackBGRT boot entry.
*/
protected void EnableEntry() {
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", true, 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();
Execute("bcdedit", "/enum firmware", true);
}
/**
* 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) {
@@ -521,13 +596,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(Path.Combine(InstallPath, $"grub{EfiArch}.efi"), msGrub, false);
InstallFile(Path.Combine(InstallPath, $"mm{EfiArch}.efi"), msMm, 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) {
@@ -538,6 +616,7 @@ public class Setup {
throw new SetupException("Rollback failed, your system may be unbootable! Create a rescue disk IMMEADIATELY!");
}
}
throw e;
}
}
@@ -545,11 +624,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);
@@ -638,10 +719,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!");
@@ -657,7 +736,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.");
@@ -681,10 +760,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.");
@@ -704,7 +781,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.");
@@ -741,6 +818,26 @@ public class Setup {
}
}
/**
* Ask if the user wants to boot to the UEFI setup.
*/
protected void AskToBootToFW() {
WriteLine();
WriteLine("Sometimes HackBGRT needs to be enabled manually in the UEFI setup.");
WriteLine("You can do that now, or later if you notice that HackBGRT isn't working.");
WriteLine("Find 'Boot options' or something similar and move HackBGRT before Windows.");
if (!Efi.CanBootToFW()) {
return;
}
WriteLine("Do you want to reboot and enter the UEFI setup now? (Y/N)");
var k = Console.ReadKey().Key;
Log($"User input: {k}");
WriteLine();
if (k == ConsoleKey.Y) {
BootToFW();
}
}
/**
* Detect the EFI architecture.
*/
@@ -863,9 +960,9 @@ public class Setup {
Configure();
}
if (k == ConsoleKey.I) {
RunPrivilegedActions(new string[] { "disable", "install", "enable-bcdedit" });
RunPrivilegedActions(new string[] { "disable", "install", "enable-bcdedit", "ask-to-boot-to-fw" });
} else if (k == ConsoleKey.J) {
RunPrivilegedActions(new string[] { "disable", "install", "enable-entry" });
RunPrivilegedActions(new string[] { "disable", "install", "enable-entry", "ask-to-boot-to-fw" });
} else if (k == ConsoleKey.O) {
RunPrivilegedActions(new string[] { "disable", "install", "enable-overwrite" });
} else if (k == ConsoleKey.F) {
@@ -911,10 +1008,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}");
@@ -925,44 +1031,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") {
@@ -979,10 +1075,13 @@ public class Setup {
Disable();
} else if (arg == "uninstall") {
Uninstall();
} else if (arg == "ask-to-boot-to-fw") {
AskToBootToFW();
} else if (arg == "boot-to-fw") {
BootToFW();
} else if (arg == "show-boot-log") {
WriteLine(bootLog);
CheckEntries();
} else {
throw new SetupException($"Invalid action: '{arg}'!");
}
@@ -999,6 +1098,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);
}
@@ -1011,14 +1111,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 {
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;
@@ -1054,7 +1164,7 @@ public class Setup {
WriteLine("This was a dry run, your system was not actually modified.");
}
if (!Batch) {
WriteLine("If you need to report a bug,\n - run this setup again with menu option L (show-boot-log)\n - then include the setup.log file with your report.");
WriteLine("If you need to report a bug,\n - run this setup again with menu option L (show-boot-log)\n - then include the setup-log.txt file with your report.");
WriteLine("Press any key to quit.");
Console.ReadKey();
}
+64 -29
View File
@@ -68,54 +68,89 @@ BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE base_dir,
return TRUE;
}
static void SetBMPWithRandom(struct HackBGRT_config* config, int weight, enum HackBGRT_action action, int x, int y, int o, const CHAR16* path) {
static void SetBMPWithRandom(struct HackBGRT_config* config, const struct HackBGRT_image_config* image, int weight) {
config->image_weight_sum += weight;
UINT32 random = (((UINT64) Random() & 0xffffffff) * config->image_weight_sum) >> 32;
UINT32 limit = ((UINT64) 0xffffffff * weight) >> 32;
Log(config->debug, L"%s n=%d, action=%d, x=%d, y=%d, o=%d, path=%s, rand=%x/%x\n", random <= limit ? L"Using" : L"Skipping", weight, action, x, y, o, path, random, limit);
Log(
config->debug,
L"image n=%d, action=%d, x=%d (mode %d), y=%d (mode %d), o=%d, path=%s, rand=%x/%x, chosen=%d\n",
weight, image->action, image->x, image->x_mode, image->y, image->y_mode, image->orientation, image->path, random, limit, (random <= limit)
);
if (random <= limit) {
config->action = action;
config->image_path = path;
config->orientation = o;
config->image_x = x;
config->image_y = y;
config->image = *image;
}
}
static int ParseCoordinate(const CHAR16* str, enum HackBGRT_action action) {
if (str && ((L'0' <= str[0] && str[0] <= L'9') || str[0] == L'-')) {
return str[0] == L'-' ? -(int)Atoi(str+1) : (int)Atoi(str);
static void ParseCoordinate(const CHAR16* str, int* out_int, enum HackBGRT_coordinate_mode* out_mode) {
if (!str) {
return;
}
if ((str && StrnCmp(str, L"keep", 4) == 0) || action == HackBGRT_KEEP) {
return HackBGRT_coord_keep;
if (StrnCmp(str, L"keep", 4) == 0) {
*out_int = 0;
*out_mode = HackBGRT_COORDINATE_MODE_KEEP;
return;
}
if (str[0] == L'.' && L'0' <= str[1] && str[1] <= L'9') {
int result = 0, i = 1, length = 0;
for (length = 0; length < HackBGRT_FRACTION_DIGITS; ++length) {
result = 10 * result;
if (L'0' <= str[i] && str[i] <= L'9') {
result += str[i] - L'0';
i += 1;
}
}
*out_mode = HackBGRT_COORDINATE_MODE_FRACTION;
*out_int = result;
return;
}
int neg = str[0] == L'-' ? 1 : 0;
if (L'0' <= str[neg] && str[neg] <= L'9') {
*out_int = Atoi(str + neg) * (neg ? -1 : 1);
*out_mode = HackBGRT_COORDINATE_MODE_CENTERED;
return;
}
return 0;
}
static void ReadConfigImage(struct HackBGRT_config* config, const CHAR16* line) {
const CHAR16* n = StrStrAfter(line, L"n=");
const CHAR16* x = StrStrAfter(line, L"x=");
const CHAR16* y = StrStrAfter(line, L"y=");
const CHAR16* o = StrStrAfter(line, L"o=");
const CHAR16* f = StrStrAfter(line, L"path=");
enum HackBGRT_action action = HackBGRT_KEEP;
if (f) {
action = HackBGRT_REPLACE;
struct HackBGRT_image_config image = {
.action = HackBGRT_ACTION_KEEP,
.orientation = HackBGRT_ORIENTATION_KEEP,
};
const CHAR16* tmp;
image.path = StrStrAfter(line, L"path=");
if (image.path) {
image.action = HackBGRT_ACTION_REPLACE;
// Default: x centered, y 38.2 % (= 1 - 1 / golden_ratio)
image.x_mode = image.y_mode = HackBGRT_COORDINATE_MODE_FRACTION;
image.x = HackBGRT_FRACTION_HALF;
image.y = HackBGRT_FRACTION_381966011;
} else if (StrStr(line, L"remove")) {
action = HackBGRT_REMOVE;
image.action = HackBGRT_ACTION_REMOVE;
} else if (StrStr(line, L"black")) {
action = HackBGRT_REPLACE;
image.action = HackBGRT_ACTION_REPLACE;
image.path = 0;
} else if (StrStr(line, L"keep")) {
action = HackBGRT_KEEP;
image.action = HackBGRT_ACTION_KEEP;
image.x_mode = image.y_mode = HackBGRT_COORDINATE_MODE_KEEP;
} else {
Log(1, L"Invalid image line: %s\n", line);
return;
}
int weight = n && (!f || n < f) ? Atoi(n) : 1;
int x_val = ParseCoordinate(x, action);
int y_val = ParseCoordinate(y, action);
int o_val = o ? ParseCoordinate(o, action) : HackBGRT_coord_keep;
SetBMPWithRandom(config, weight, action, x_val, y_val, o_val, f);
ParseCoordinate(StrStrAfter(line, L"x="), &image.x, &image.x_mode);
ParseCoordinate(StrStrAfter(line, L"y="), &image.y, &image.y_mode);
if (StrStrAfter(line, L"o=keep")) {
image.orientation = HackBGRT_ORIENTATION_KEEP;
} else if ((tmp = StrStrAfter(line, L"o="))) {
// convert orientation in degrees to number 0-3 (* 90 degrees)
int i = tmp[0] == L'-' ? -(int)Atoi(tmp+1) : (int)Atoi(tmp);
image.orientation = (i / 90) & 3;
}
int weight = 1;
if ((tmp = StrStrAfter(line, L"n="))) {
weight = Atoi(tmp);
}
SetBMPWithRandom(config, &image, weight);
}
static void ReadConfigResolution(struct HackBGRT_config* config, const CHAR16* line) {
+36 -8
View File
@@ -6,15 +6,47 @@
* Possible actions to perform on the BGRT.
*/
enum HackBGRT_action {
HackBGRT_KEEP = 0, HackBGRT_REPLACE, HackBGRT_REMOVE
HackBGRT_ACTION_KEEP = 0,
HackBGRT_ACTION_REPLACE,
HackBGRT_ACTION_REMOVE
};
/**
* Special values for the image coordinates.
* @see struct HackBGRT_config
*/
enum HackBGRT_coordinate {
HackBGRT_coord_keep = -1000001
enum HackBGRT_coordinate_mode {
HackBGRT_COORDINATE_MODE_KEEP = 0,
HackBGRT_COORDINATE_MODE_CENTERED,
HackBGRT_COORDINATE_MODE_FRACTION,
};
/**
* Constants for the fractional coordinates.
*/
enum HackBGRT_fraction {
HackBGRT_FRACTION_DIGITS = 4,
HackBGRT_FRACTION_ONE = 10000,
HackBGRT_FRACTION_HALF = 5000,
HackBGRT_FRACTION_381966011 = 3820,
};
/**
* Possible values for the orientation.
*/
enum HackBGRT_orientation {
HackBGRT_ORIENTATION_KEEP = -1,
};
/**
* The configuration for one image.
*/
struct HackBGRT_image_config {
enum HackBGRT_action action;
const CHAR16* path;
enum HackBGRT_coordinate_mode x_mode, y_mode;
int x, y;
int orientation;
};
/**
@@ -22,12 +54,8 @@ enum HackBGRT_coordinate {
*/
struct HackBGRT_config {
int debug, log;
enum HackBGRT_action action;
const CHAR16* image_path;
int image_x;
int image_y;
struct HackBGRT_image_config image;
int image_weight_sum;
int orientation;
int resolution_x;
int resolution_y;
int old_resolution_x;
+27
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) {
+53 -22
View File
@@ -21,7 +21,6 @@ EFI_RUNTIME_SERVICES *RT;
*/
static struct HackBGRT_config config = {
.log = 1,
.action = HackBGRT_KEEP,
};
/**
@@ -163,13 +162,13 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
}
Log(config.debug, L" - ACPI table @%x: %s, revision = %d, OEM ID = %s\n", (UINTN)entry, TmpStr(entry->signature, 4), entry->revision, TmpStr(entry->oem_id, 6));
switch (action) {
case HackBGRT_KEEP:
case HackBGRT_ACTION_KEEP:
if (!bgrt) {
Log(config.debug, L" -> Returning this one for later use.\n");
bgrt = (ACPI_BGRT*) entry;
}
break;
case HackBGRT_REMOVE:
case HackBGRT_ACTION_REMOVE:
Log(config.debug, L" -> Deleting.\n");
for (int k = j+1; k < entry_arr_length; ++k) {
entry_arr[k-1] = entry_arr[k];
@@ -179,13 +178,13 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
xsdt->length -= sizeof(entry_arr[0]);
--j;
break;
case HackBGRT_REPLACE:
case HackBGRT_ACTION_REPLACE:
Log(config.debug, L" -> Replacing.\n");
entry_arr[j] = (UINTN) bgrt;
}
bgrt_count += 1;
}
if (!bgrt_count && action == HackBGRT_REPLACE && bgrt) {
if (!bgrt_count && action == HackBGRT_ACTION_REPLACE && bgrt) {
Log(config.debug, L" - Adding missing BGRT.\n");
xsdt = CreateXsdt(xsdt, entry_arr_length + 1);
entry_arr = (UINT64*)&xsdt[1];
@@ -303,13 +302,13 @@ static void CropBMP(BMP* bmp, int w, int h) {
*/
void HackBgrt(EFI_FILE_HANDLE base_dir) {
// REMOVE: simply delete all BGRT entries.
if (config.action == HackBGRT_REMOVE) {
HandleAcpiTables(config.action, 0);
if (config.image.action == HackBGRT_ACTION_REMOVE) {
HandleAcpiTables(config.image.action, 0);
return;
}
// KEEP/REPLACE: first get the old BGRT entry.
ACPI_BGRT* bgrt = HandleAcpiTables(HackBGRT_KEEP, 0);
ACPI_BGRT* bgrt = HandleAcpiTables(HackBGRT_ACTION_KEEP, 0);
// Get the old BMP and position (relative to screen center), if possible.
const int old_valid = bgrt && VerifyAcpiSdtChecksum(bgrt);
@@ -324,7 +323,7 @@ void HackBgrt(EFI_FILE_HANDLE base_dir) {
// Missing BGRT?
if (!bgrt) {
// Keep missing = do nothing.
if (config.action == HackBGRT_KEEP) {
if (config.image.action == HackBGRT_ACTION_KEEP) {
return;
}
// Replace missing = allocate new.
@@ -351,13 +350,13 @@ void HackBgrt(EFI_FILE_HANDLE base_dir) {
// Get the image (either old or new).
BMP* new_bmp = old_bmp;
if (config.action == HackBGRT_REPLACE) {
new_bmp = LoadBMP(base_dir, config.image_path);
if (config.image.action == HackBGRT_ACTION_REPLACE) {
new_bmp = LoadBMP(base_dir, config.image.path);
}
// No image = no need for BGRT.
if (!new_bmp) {
HandleAcpiTables(HackBGRT_REMOVE, 0);
HandleAcpiTables(HackBGRT_ACTION_REMOVE, 0);
return;
}
@@ -366,16 +365,23 @@ void HackBgrt(EFI_FILE_HANDLE base_dir) {
// Set the image address and orientation.
bgrt->image_address = (UINTN) new_bmp;
const int new_orientation = config.orientation == HackBGRT_coord_keep ? old_orientation : ((config.orientation / 90) & 3);
const int new_orientation = config.image.orientation == HackBGRT_ORIENTATION_KEEP ? old_orientation : config.image.orientation;
bgrt->status = new_orientation << 1;
// New center coordinates.
const int new_x = config.image_x == HackBGRT_coord_keep ? old_x : config.image_x;
const int new_y = config.image_y == HackBGRT_coord_keep ? old_y : config.image_y;
const int new_swap = new_orientation & 1;
const int new_reso_x = new_swap ? config.resolution_y : config.resolution_x;
const int new_reso_y = new_swap ? config.resolution_x : config.resolution_y;
const int new_x =
config.image.x_mode == HackBGRT_COORDINATE_MODE_KEEP ? old_x :
config.image.x_mode == HackBGRT_COORDINATE_MODE_CENTERED ? config.image.x :
(config.image.x - HackBGRT_FRACTION_HALF) * new_reso_x / HackBGRT_FRACTION_ONE;
const int new_y =
config.image.y_mode == HackBGRT_COORDINATE_MODE_KEEP ? old_y :
config.image.y_mode == HackBGRT_COORDINATE_MODE_CENTERED ? config.image.y :
(config.image.y - HackBGRT_FRACTION_HALF) * new_reso_y / HackBGRT_FRACTION_ONE;
// Calculate absolute position.
const int max_x = new_reso_x - new_bmp->width;
const int max_y = new_reso_y - new_bmp->height;
@@ -383,15 +389,17 @@ void HackBgrt(EFI_FILE_HANDLE base_dir) {
bgrt->image_offset_y = max(0, min(max_y, new_y + (new_reso_y - new_bmp->height) / 2));
Log(config.debug,
L"BMP at (%d, %d), center (%d, %d), resolution (%d, %d), orientation %d.\n",
L"Screen %dx%d, BMP %dx%d, center (%d, %d) = corner (%d, %d), orientation %d.\n",
new_reso_x, new_reso_y,
new_bmp->width, new_bmp->height,
new_x, new_y,
(int) bgrt->image_offset_x, (int) bgrt->image_offset_y,
new_x, new_y, new_reso_x, new_reso_y,
new_orientation * 90
);
// Store this BGRT in the ACPI tables.
SetAcpiSdtChecksum(bgrt);
HandleAcpiTables(HackBGRT_REPLACE, bgrt);
HandleAcpiTables(HackBGRT_ACTION_REPLACE, bgrt);
}
/**
@@ -439,16 +447,39 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
goto fail;
}
EFI_FILE_HANDLE base_dir;
if (EFI_ERROR(root_dir->Open(root_dir, &base_dir, L"\\EFI\\HackBGRT", EFI_FILE_MODE_READ, 0))) {
Log(config.debug, L"Failed to HackBGRT directory.\n");
base_dir = root_dir;
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"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;
}