mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-07 09:36:10 -08:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9e23c91a3 | ||
|
|
697c57355b | ||
|
|
da16365508 | ||
|
|
665a4732ca | ||
|
|
39596aadfc | ||
|
|
1a5b1df064 | ||
|
|
ea70f3ce79 | ||
|
|
a44b929012 | ||
|
|
9948e5a306 | ||
|
|
518d7c8a97 | ||
|
|
c6108ffd62 | ||
|
|
6dc447a8ce | ||
|
|
5ec17a49e8 | ||
|
|
7b7309a255 | ||
|
|
a82646a822 | ||
|
|
294da9c069 | ||
|
|
4096002eb2 | ||
|
|
835cc1b2f2 | ||
|
|
74a143e723 | ||
|
|
b75dbe13c7 | ||
|
|
ba72b90082 | ||
|
|
6b724d5590 | ||
|
|
9ebe4e2476 | ||
|
|
5111897fae | ||
|
|
aac8a38cbb | ||
|
|
77dd2bd699 | ||
|
|
8dfa456a7b | ||
|
|
7ccdcc4a77 | ||
|
|
50e84e8c90 | ||
|
|
24c7e2b316 | ||
|
|
4379f9cbeb | ||
|
|
2a0f2a7757 | ||
|
|
ebbacb72bb | ||
|
|
a908036ee8 | ||
|
|
57ce3ae33e | ||
|
|
7dd048346d | ||
|
|
0dfc49c800 | ||
|
|
4e50b33636 | ||
|
|
5856d25b5b | ||
|
|
466ab69c48 | ||
|
|
14aa79929a | ||
|
|
5e32a3f880 | ||
|
|
db934099df | ||
|
|
e93ed54cb2 | ||
|
|
9b3b045a21 | ||
|
|
be8a5d35d2 | ||
|
|
2366fc8b98 | ||
|
|
e04ba3e451 | ||
|
|
f40f2bc9dc | ||
|
|
2f572b24d4 | ||
|
|
da9909bbdd | ||
|
|
0ce904f133 | ||
|
|
62c892009a | ||
|
|
00bbfd6d82 | ||
|
|
1e566a05a0 | ||
|
|
1058f3e2b4 | ||
|
|
661758ba58 | ||
|
|
93eec1a250 | ||
|
|
33e4450d19 | ||
|
|
99bdf5a310 | ||
|
|
d2bac39680 | ||
|
|
a4c917294c | ||
|
|
d6da4b7cee | ||
|
|
f2185f624d | ||
|
|
75e64a7ef3 | ||
|
|
e5616c6cd2 | ||
|
|
ad0b71c49b | ||
|
|
e44ce9f5ee | ||
|
|
5f3c6afc23 | ||
|
|
1fa53f3f06 | ||
|
|
8301a16ca8 | ||
|
|
de9d0d984a | ||
|
|
7dbdf33ea8 | ||
|
|
dfb5b916ed | ||
|
|
267af0bd9c | ||
|
|
be5894b387 | ||
|
|
199650a567 | ||
|
|
31172f71ca | ||
|
|
990f245ac9 | ||
|
|
dfadf67a21 | ||
|
|
691fbd164b | ||
|
|
7d5f4eac17 | ||
|
|
0b5899e801 | ||
|
|
4cf12f26b2 | ||
|
|
3396a4799d | ||
|
|
d35a9abb0a | ||
|
|
85811d62a6 | ||
|
|
449dc6acc6 | ||
|
|
1980e5c05c | ||
|
|
9a59f69a28 | ||
|
|
1fffbcff2c | ||
|
|
bd7a5f3eea | ||
|
|
32643fab96 | ||
|
|
ecbca09419 | ||
|
|
fa0f846f79 | ||
|
|
b469b600ba | ||
|
|
f7fa54cfee | ||
|
|
e092c4768c | ||
|
|
7ad4762a3d | ||
|
|
cea656631a | ||
|
|
1b6b17ec9a | ||
|
|
91aad3b971 |
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report if you encounter a bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What happens?**
|
||||
Describe the bug.
|
||||
|
||||
**How this happens?**
|
||||
Explain exactly the steps you did:
|
||||
1. Download HackBGRT-(version).zip and extract all files.
|
||||
2. Start setup.
|
||||
3. Select ...
|
||||
4. ...
|
||||
|
||||
**Log file**
|
||||
Run the setup again and select menu option `L` (or add parameter `show-boot-log` on command line). Attach the `setup.log` to this report.
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "gnu-efi"]
|
||||
path = gnu-efi
|
||||
url = https://github.com/rhboot/gnu-efi.git
|
||||
73
CHANGELOG.md
73
CHANGELOG.md
@@ -2,6 +2,79 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 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
|
||||
- BitLocker detection is more reliable.
|
||||
|
||||
## 2.3.0 - 2023-11-27
|
||||
|
||||
### Added
|
||||
- AArch64 and ARM builds, and *shim* for AArch64.
|
||||
|
||||
### Fixed
|
||||
- Boot entry is more reliable, avoids conflicts with firmware entries.
|
||||
|
||||
## 2.2.0 - 2023-11-17
|
||||
|
||||
### Added
|
||||
- Support Secure Boot with *shim* boot loader.
|
||||
- Gather debug log during boot and read it with setup.exe.
|
||||
|
||||
## 2.1.0 - 2023-10-04
|
||||
|
||||
### Added
|
||||
- Check image size, crop if it's bigger than the screen.
|
||||
- Check BitLocker status to avoid unbootable machine.
|
||||
|
||||
## 2.0.0 - 2023-09-10
|
||||
|
||||
### Added
|
||||
- Log to `setup.log`.
|
||||
- Image conversion (GIF, EXIF, JPG, PNG, TIFF) to BMP during setup.
|
||||
- Quiet (batch) setup.
|
||||
- Dry run in setup.
|
||||
- EFI boot entry support in setup.
|
||||
- Orientation parameter (o=0|90|180|270) for images.
|
||||
|
||||
### Changed
|
||||
- Configure (edit config and images) before installing.
|
||||
- Escalate privileges only when needed (after the menu).
|
||||
- Try to detect and avoid some configuration errors.
|
||||
- Wait at most 15 seconds for key presses during boot.
|
||||
- Image coordinates are now relative to the center.
|
||||
|
||||
## 1.5.1 - 2018-08-11
|
||||
|
||||
### Fixed
|
||||
- Clarify the default config.txt.
|
||||
- Fix an exception in some cases when trying to boot to UEFI setup.
|
||||
|
||||
## 1.5.0 - 2017-09-30
|
||||
|
||||
### Added
|
||||
- Support for rebooting to UEFI setup.
|
||||
|
||||
### Changed
|
||||
- Minor enhancements to installer.
|
||||
|
||||
## 1.4.0 - 2017-08-29
|
||||
|
||||
### Added
|
||||
|
||||
143
Makefile
143
Makefile
@@ -1,43 +1,122 @@
|
||||
CC = $(CC_PREFIX)-gcc
|
||||
CFLAGS = -std=c11 -O2 -ffreestanding -mno-red-zone -fno-stack-protector -Wshadow -Wall -Wunused -Werror-implicit-function-declaration -Werror
|
||||
CC = clang
|
||||
CFLAGS = -target $(CLANG_TARGET) -ffreestanding -fshort-wchar
|
||||
CFLAGS += -std=c17 -Wshadow -Wall -Wunused -Werror-implicit-function-declaration
|
||||
CFLAGS += -I$(GNUEFI_INC) -I$(GNUEFI_INC)/$(GNUEFI_ARCH) -I$(GNUEFI_INC)/protocol
|
||||
LDFLAGS = -nostdlib -shared -Wl,-dll -Wl,--subsystem,10 -e _EfiMain
|
||||
LIBS = -L$(GNUEFI_LIB) -lefi -lgcc
|
||||
CFLAGS += $(ARCH_CFLAGS)
|
||||
LDFLAGS = -target $(CLANG_TARGET) -nostdlib -Wl,-entry:efi_main -Wl,-subsystem:efi_application -fuse-ld=lld
|
||||
ARCH_CFLAGS = -O2 -mno-red-zone
|
||||
|
||||
GNUEFI_INC = /usr/$(CC_PREFIX)/include/efi
|
||||
GNUEFI_LIB = /usr/$(CC_PREFIX)/lib
|
||||
GNUEFI_INC = gnu-efi/inc
|
||||
|
||||
FILES_C = src/main.c src/util.c src/types.c src/config.c
|
||||
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)
|
||||
GIT_DESCRIBE = $(firstword $(shell git describe --tags) unknown)
|
||||
CFLAGS += '-DGIT_DESCRIBE=L"$(GIT_DESCRIBE)"'
|
||||
ZIPDIR = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||
ZIP = $(ZIPDIR).zip
|
||||
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs
|
||||
GIT_DESCRIBE := $(firstword $(GIT_DESCRIBE) $(shell git describe --tags) unknown)
|
||||
CFLAGS += '-DGIT_DESCRIBE_W=L"$(GIT_DESCRIBE)"' '-DGIT_DESCRIBE="$(GIT_DESCRIBE)"'
|
||||
RELEASE_NAME = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||
|
||||
EFI_ARCH_LIST = x64 ia32 aa64 arm
|
||||
EFI_SIGNED_FILES = $(patsubst %,efi-signed/boot%.efi,$(EFI_ARCH_LIST))
|
||||
|
||||
.PHONY: all efi efi-signed setup release clean
|
||||
|
||||
efi: bootx64.efi bootia32.efi
|
||||
setup: setup.exe
|
||||
all: efi setup
|
||||
@echo "Run 'make efi-signed' to sign the EFI executables."
|
||||
@echo "Run 'make release' to build a release-ready ZIP archive."
|
||||
@echo "Run 'make run-qemu-<arch>' to test the EFI executables with QEMU."
|
||||
|
||||
zip: $(ZIP)
|
||||
$(ZIP): bootx64.efi bootia32.efi config.txt splash.bmp setup.exe README.md CHANGELOG.md README.efilib LICENSE
|
||||
test ! -d "$(ZIPDIR)"
|
||||
mkdir "$(ZIPDIR)"
|
||||
cp -a $^ "$(ZIPDIR)" || (rm -rf "$(ZIPDIR)"; exit 1)
|
||||
7z a -mx=9 "$(ZIP)" "$(ZIPDIR)" || (rm -rf "$(ZIPDIR)"; exit 1)
|
||||
rm -rf "$(ZIPDIR)"
|
||||
efi: $(patsubst %,efi/boot%.efi,$(EFI_ARCH_LIST))
|
||||
@echo "EFI executables are in the efi/ directory."
|
||||
|
||||
src/GIT_DESCRIBE.cs: src/Setup.cs $(FILES_C) $(FILES_H)
|
||||
echo 'public class GIT_DESCRIBE { public static string data = "$(GIT_DESCRIBE)"; }' > $@
|
||||
efi-signed: $(patsubst %,efi-signed/boot%.efi,$(EFI_ARCH_LIST))
|
||||
@echo "Signed EFI executables are in the efi-signed/ directory."
|
||||
|
||||
setup.exe: src/Setup.cs src/GIT_DESCRIBE.cs
|
||||
mcs -define:GIT_DESCRIBE -out:$@ $^
|
||||
setup: setup.exe
|
||||
|
||||
bootx64.efi: CC_PREFIX = x86_64-w64-mingw32
|
||||
bootx64.efi: GNUEFI_ARCH = x86_64
|
||||
bootx64.efi: $(FILES_C)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) -s
|
||||
release: release/$(RELEASE_NAME).zip
|
||||
@echo "Current version is packaged: $<"
|
||||
|
||||
bootia32.efi: CC_PREFIX = i686-w64-mingw32
|
||||
bootia32.efi: GNUEFI_ARCH = ia32
|
||||
bootia32.efi: $(FILES_C)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) -s
|
||||
release/$(RELEASE_NAME): $(EFI_SIGNED_FILES) certificate.cer config.txt splash.bmp setup.exe README.md CHANGELOG.md README.efilib LICENSE shim-signed/* shim.md
|
||||
rm -rf $@
|
||||
tar c --transform=s,^,$@/, $^ | tar x
|
||||
|
||||
release/$(RELEASE_NAME).zip: release/$(RELEASE_NAME)
|
||||
rm -rf $@
|
||||
(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)"; }' > $@
|
||||
|
||||
setup.exe: $(FILES_CS) src/GIT_DESCRIBE.cs
|
||||
csc /nologo /define:GIT_DESCRIBE /out:$@ $^
|
||||
|
||||
certificate.cer pki:
|
||||
@echo
|
||||
@echo "You need proper keys to sign the EFI executables."
|
||||
@echo "Example:"
|
||||
@echo "mkdir -p pki"
|
||||
@echo "certutil --empty-password -N -d pki"
|
||||
@echo "efikeygen -d pki -n HackBGRT-signer -S -k -c 'CN=HackBGRT Secure Boot Signer,OU=HackBGRT,O=Unknown,MAIL=unknown@example.com' -u 'URL'"
|
||||
@echo "certutil -d pki -n HackBGRT-signer -Lr > certificate.cer"
|
||||
@echo "Modify and run the commands yourself."
|
||||
@echo
|
||||
@false
|
||||
|
||||
efi-signed/%.efi: efi/%.efi pki
|
||||
@mkdir -p efi-signed
|
||||
pesign --force -n pki -i $< -o $@ -c HackBGRT-signer -s
|
||||
|
||||
efi/bootx64.efi: CLANG_TARGET = x86_64-pc-windows-msvc
|
||||
efi/bootx64.efi: GNUEFI_ARCH = x86_64
|
||||
|
||||
efi/bootia32.efi: CLANG_TARGET = i386-pc-windows-msvc
|
||||
efi/bootia32.efi: GNUEFI_ARCH = ia32
|
||||
|
||||
efi/bootaa64.efi: CLANG_TARGET = aarch64-pc-windows-msvc
|
||||
efi/bootaa64.efi: GNUEFI_ARCH = aa64
|
||||
|
||||
efi/boot%.efi: $(FILES_C)
|
||||
@mkdir -p efi
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
|
||||
|
||||
efi/bootarm.efi: CLANG_TARGET = armv6-pc-windows-msvc
|
||||
efi/bootarm.efi: GNUEFI_ARCH = arm
|
||||
efi/bootarm.efi: ARCH_CFLAGS = -O # skip -O2 and -mno-red-zone
|
||||
efi/bootarm.efi: $(FILES_C)
|
||||
@mkdir -p efi
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
|
||||
@echo "Fix $@ architecture code (IMAGE_FILE_MACHINE_ARMTHUMB_MIXED = 0x01C2)"
|
||||
echo -en "\xc2\x01" | dd of=$@ bs=1 seek=124 count=2 conv=notrunc status=none
|
||||
|
||||
clean:
|
||||
rm -rf setup.exe efi efi-signed
|
||||
rm -f src/GIT_DESCRIBE.cs
|
||||
rm -rf release
|
||||
rm -rf test
|
||||
|
||||
.PHONY: test $(patsubst %,run-qemu-%,$(EFI_ARCH_LIST))
|
||||
|
||||
test: run-qemu-x64
|
||||
@echo "Run 'make run-qemu-<arch>' to test other architectures."
|
||||
|
||||
test/esp-%: efi/boot%.efi splash.bmp
|
||||
rm -rf $@
|
||||
mkdir -p $@/EFI/HackBGRT
|
||||
cp efi/boot$*.efi splash.bmp $@/EFI/HackBGRT
|
||||
echo -en "FS0:\n cd EFI\n cd HackBGRT\n boot$*.efi resolution=-1x-1 debug=1 image=path=splash.bmp" > $@/startup.nsh
|
||||
|
||||
QEMU_ARGS = -bios $(word 2, $^) -net none -drive media=disk,file=fat:rw:./$<,format=raw
|
||||
|
||||
run-qemu-x64: test/esp-x64 /usr/share/ovmf/x64/OVMF.fd
|
||||
qemu-system-x86_64 $(QEMU_ARGS)
|
||||
|
||||
run-qemu-ia32: test/esp-ia32 /usr/share/ovmf/ia32/OVMF.fd
|
||||
qemu-system-i386 $(QEMU_ARGS)
|
||||
|
||||
run-qemu-aa64: test/esp-aa64 /usr/share/ovmf/aarch64/QEMU_EFI.fd
|
||||
@echo "Press Ctrl+Alt+2 to switch to QEMU console."
|
||||
qemu-system-aarch64 -machine virt -cpu max $(QEMU_ARGS)
|
||||
|
||||
run-qemu-arm: test/esp-arm /usr/share/ovmf/arm/QEMU_EFI.fd
|
||||
@echo "Press Ctrl+Alt+2 to switch to QEMU console."
|
||||
qemu-system-arm -machine virt -cpu max $(QEMU_ARGS)
|
||||
|
||||
91
README.md
91
README.md
@@ -4,55 +4,86 @@ HackBGRT is intended as a boot logo changer for UEFI-based Windows systems.
|
||||
|
||||
## Summary
|
||||
|
||||
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 permamently, but a custom UEFI application may be used to overwrite it during the boot. HackBGRT does exactly that.
|
||||
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.
|
||||
|
||||
* Make sure that your computer is booting with UEFI.
|
||||
* Make sure that Secure Boot is disabled, or learn to sign EFI applications.
|
||||
* Simple Windows installation:
|
||||
* Get the latest release.
|
||||
* Start `setup.exe` and select `I` (install).
|
||||
* The installer will launch Notepad for modifying the configuration.
|
||||
* If you need only one custom image, the defaults are fine.
|
||||
* Otherwise, check out the examples in the configuration file.
|
||||
* The installer will launch Paint for creating the image(s).
|
||||
* You can create multiple images by using Save As.
|
||||
* Be sure to always use the 24-bit BMP/DIB format.
|
||||
* If Windows later restores the original boot loader, simply reinstall.
|
||||
* If you wish to change the image or other configuration, simply reinstall.
|
||||
* Installation for Windows with another boot loader (e.g. GRUB):
|
||||
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`.
|
||||
* Set `boot=\EFI\Microsoft\Boot\bootmgfw.efi` in `config.txt`.
|
||||
* Point your boot loader to `\EFI\HackBGRT\bootx64.efi`.
|
||||
* Installation for all operating systems:
|
||||
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`.
|
||||
* Set `boot=` to your preferred boot loader in `config.txt`.
|
||||
* Set `\EFI\HackBGRT\bootx64.efi` as your default boot loader with `efibootmgr` or some other EFI boot manager tool.
|
||||
* Make sure that you have read the Secure Boot instructions.
|
||||
* Make sure that BitLocker is disabled, or find your recovery key.
|
||||
|
||||
### Secure Boot instructions
|
||||
|
||||
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).
|
||||
|
||||
### Windows installation
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
### 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.
|
||||
* `enable-overwrite` – overwrite the MS boot loader.
|
||||
* `disable-overwrite` – restore the MS boot loader.
|
||||
* `skip-shim` – skip *shim* when installing.
|
||||
* `allow-secure-boot` – ignore Secure Boot in subsequent commands.
|
||||
* `allow-bitlocker` – ignore BitLocker in subsequent commands.
|
||||
* `allow-bad-loader` – ignore bad boot loader configuration in subsequent commands.
|
||||
* `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.
|
||||
|
||||
### Multi-boot configurations
|
||||
|
||||
If you only need HackBGRT for Windows:
|
||||
|
||||
* Run `setup.exe`, install files without enabling.
|
||||
* Configure your boot loader to start `\EFI\HackBGRT\loader.efi`.
|
||||
|
||||
If you need it for other systems as well:
|
||||
|
||||
* Configure HackBGRT to start your boot loader (such as systemd-boot): `boot=\EFI\systemd\systemd-bootx64.efi`.
|
||||
* Run `setup.exe`, install as a new EFI boot entry.
|
||||
|
||||
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.
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration options are described in `config.txt`, which should be stored in `[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`.
|
||||
|
||||
## 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 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. 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.
|
||||
|
||||
Multiple images may be specified, in which case one is picked at random.
|
||||
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
|
||||
|
||||
If something breaks and you can't boot to Windows, you have the following options:
|
||||
|
||||
* Windows installation (or recovery) media can fix boot issues.
|
||||
* You can copy `[EFI System Partition]\EFI\HackBGRT\bootmgfw-original.efi` into `[EFI System Partition]\EFI\Microsoft\Boot\bootmgfw.efi` by some other means such as Linux or Windows command prompt.
|
||||
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.
|
||||
|
||||
## Building
|
||||
|
||||
* Compiler: GCC targeting w64-mingw32
|
||||
* Compiler: Clang
|
||||
* Compiler flags: see Makefile
|
||||
* Libraries: gnu-efi
|
||||
|
||||
39
config.txt
Executable file → Normal file
39
config.txt
Executable file → Normal file
@@ -1,35 +1,46 @@
|
||||
# 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.
|
||||
|
||||
# Boot loader path. Point to the real Windows boot loader.
|
||||
boot=\EFI\HackBGRT\bootmgfw-original.efi
|
||||
# Boot loader path. MS = either backup or original Windows boot loader.
|
||||
boot=MS
|
||||
|
||||
# The image is specified with an image line.
|
||||
# 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=[0-9]+", a weight for this image in the randomization process. Default: n=1.
|
||||
# - "x={auto|native|[0-9]+}", the x coordinate. Default: x=auto.
|
||||
# - "y={auto|native|[0-9]+}", the y coordinate. Default: y=auto.
|
||||
# - "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.
|
||||
# One of the following:
|
||||
# - "keep" to keep the firmware logo. Sets also x=native,y=native by default.
|
||||
# - "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. The file must be a 24-bit BMP file with a 54-byte header.
|
||||
# - "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=auto,y=0,path=\EFI\HackBGRT\topimage.bmp
|
||||
# - image=n=1,path=\EFI\HackBGRT\splash.bmp
|
||||
# - image=n=50,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
|
||||
# - 1/54 chance for topimage.bmp, centered at the top of the screen
|
||||
# - 1/54 chance for splash.bmp, automatically positioned
|
||||
# - 50/54 chance for probable.bmp, automatically positioned
|
||||
# - 1/54 chance for topimage.bmp, 200 px above the center of the screen
|
||||
# - 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=path=\EFI\HackBGRT\splash.bmp
|
||||
image= y=-200 path=splash.bmp
|
||||
|
||||
# Preferred resolution. Use 0x0 for maximum and -1x-1 for original.
|
||||
resolution=0x0
|
||||
|
||||
# Logging (0 for disabled, 1 for enabled).
|
||||
# When logging is enabled, setup.exe can show debug information about the current boot.
|
||||
# The log might occupy a few kilobytes of RAM.
|
||||
log=1
|
||||
|
||||
# Debug mode (0 for disabled, 1 for enabled).
|
||||
# Shows debug information and prompts for keypress before booting.
|
||||
debug=0
|
||||
|
||||
1
gnu-efi
Submodule
1
gnu-efi
Submodule
Submodule gnu-efi added at 965f557ab7
55
shim-signed/COPYRIGHT
Normal file
55
shim-signed/COPYRIGHT
Normal file
@@ -0,0 +1,55 @@
|
||||
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
|
||||
|
||||
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
|
||||
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.
|
||||
BIN
shim-signed/mmaa64.efi
Normal file
BIN
shim-signed/mmaa64.efi
Normal file
Binary file not shown.
BIN
shim-signed/mmia32.efi
Normal file
BIN
shim-signed/mmia32.efi
Normal file
Binary file not shown.
BIN
shim-signed/mmx64.efi
Normal file
BIN
shim-signed/mmx64.efi
Normal file
Binary file not shown.
BIN
shim-signed/shimaa64.efi
Normal file
BIN
shim-signed/shimaa64.efi
Normal file
Binary file not shown.
BIN
shim-signed/shimia32.efi
Normal file
BIN
shim-signed/shimia32.efi
Normal file
Binary file not shown.
BIN
shim-signed/shimx64.efi
Normal file
BIN
shim-signed/shimx64.efi
Normal file
Binary file not shown.
286
shim.md
Normal file
286
shim.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Secure Boot and *shim*
|
||||
|
||||
Secure Boot accepts only trusted files during boot. The *shim* boot loader is a tool which allows you to select which files to trust. HackBGRT installs *shim* for you, but you need to configure it with these instructions.
|
||||
|
||||
On the first boot after installing HackBGRT, you will see an error message saying "Verification failed". You need to press a key to enter the setup tool (MOKManager) where you can choose to trust HackBGRT. After that, use arrow keys to navigate and *Enter* to continue as described below.
|
||||
|
||||
## 1. Verification failed
|
||||
|
||||
This is the start of *shim* configuration.
|
||||
|
||||
```
|
||||
ERROR
|
||||
Verification failed: (0x1A) Security Violation
|
||||
+----+
|
||||
| OK |
|
||||
+----+
|
||||
```
|
||||
|
||||
Select `OK`, *Enter*.
|
||||
|
||||
```
|
||||
Shim UEFI key management
|
||||
Press any key to perform MOK management
|
||||
Booting in 5 seconds
|
||||
```
|
||||
|
||||
Press a key quickly to enter *MOK management* or the *MOKManager* program.
|
||||
|
||||
## 2. MOK management
|
||||
|
||||
```
|
||||
Perform MOK management
|
||||
|
||||
Continue to boot
|
||||
Enroll key from disk
|
||||
Enroll hash from disk
|
||||
```
|
||||
|
||||
Select `Enroll hash from disk`, *Enter*. This is the safest option where you choose to trust only a specific version of HackBGRT.
|
||||
|
||||
You can also choose to `Enroll key from disk`, which means that you choose to trust anything signed with the same certificate. How do you know if it's safe? You don't – that's why you should rather use the other option or build your own version of HackBGRT with your own certificate.
|
||||
|
||||
## 3a. Enroll hash from disk
|
||||
|
||||
```
|
||||
Select Binary
|
||||
|
||||
The Selected Binary will have its hash Enrolled
|
||||
This means it will subsequently Boot with no prompting
|
||||
Remember to make sure it is a genuine binary before enrolling its hash
|
||||
|
||||
+----------------+
|
||||
| YOUR DISK NAME |
|
||||
+----------------+
|
||||
```
|
||||
|
||||
Select the disk, *Enter*.
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| EFI/ |
|
||||
| loader/ |
|
||||
| vmlinuz-linux |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Select `EFI/`, *Enter*.
|
||||
|
||||
```
|
||||
+------------+
|
||||
| ../ |
|
||||
| Boot/ |
|
||||
| HackBGRT/ |
|
||||
| Microsoft/ |
|
||||
+------------+
|
||||
```
|
||||
|
||||
Select `HackBGRT/`, *Enter*.
|
||||
|
||||
```
|
||||
+-----------------+
|
||||
| ../ |
|
||||
| grubx64.efi |
|
||||
| loader.efi |
|
||||
| mmx64.efi |
|
||||
| certificate.cer |
|
||||
| splash.bmp |
|
||||
| config.txt |
|
||||
+-----------------+
|
||||
```
|
||||
|
||||
Select `grubx64.efi`, *Enter*.
|
||||
|
||||
```
|
||||
[Enroll MOK]
|
||||
|
||||
+------------+
|
||||
| View key 0 |
|
||||
| Continue |
|
||||
+------------+
|
||||
```
|
||||
|
||||
To verify the key contents, select `View key 0`, *Enter*.
|
||||
|
||||
```
|
||||
SHA256 hash
|
||||
(some hexadecimal values)
|
||||
```
|
||||
|
||||
Press *Enter* to continue.
|
||||
|
||||
```
|
||||
[Enroll MOK]
|
||||
|
||||
+------------+
|
||||
| View key 0 |
|
||||
| Continue |
|
||||
+------------+
|
||||
```
|
||||
|
||||
Select `Continue`, *Enter*.
|
||||
|
||||
```
|
||||
Enroll the key(s)?
|
||||
|
||||
+-----+
|
||||
| No |
|
||||
| Yes |
|
||||
+-----+
|
||||
```
|
||||
|
||||
Select `Yes`, *Enter*.
|
||||
|
||||
```
|
||||
Perform MOK management
|
||||
|
||||
+-----------------------+
|
||||
| Reboot |
|
||||
| Enroll key from disk |
|
||||
| Enroll hash from disk |
|
||||
+-----------------------+
|
||||
```
|
||||
|
||||
Select `Reboot`, *Enter*.
|
||||
|
||||
You are now ready to boot using HackBGRT.
|
||||
|
||||
## 3b. Enroll key from disk
|
||||
|
||||
```
|
||||
Select Key
|
||||
|
||||
The selected key will be enrolled into the MOK database
|
||||
This means any binaries signed with it will be run without prompting
|
||||
Remember to make sure it is a genuine key before Enrolling it
|
||||
|
||||
+----------------+
|
||||
| YOUR DISK NAME |
|
||||
+----------------+
|
||||
```
|
||||
|
||||
Select the disk, *Enter*.
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| EFI/ |
|
||||
| loader/ |
|
||||
| vmlinuz-linux |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Select `EFI/`, *Enter*.
|
||||
|
||||
```
|
||||
+------------+
|
||||
| ../ |
|
||||
| Boot/ |
|
||||
| HackBGRT/ |
|
||||
| Microsoft/ |
|
||||
+------------+
|
||||
```
|
||||
|
||||
Select `HackBGRT/`, *Enter*.
|
||||
|
||||
```
|
||||
+-----------------+
|
||||
| ../ |
|
||||
| grubx64.efi |
|
||||
| loader.efi |
|
||||
| mmx64.efi |
|
||||
| certificate.cer |
|
||||
| splash.bmp |
|
||||
| config.txt |
|
||||
+-----------------+
|
||||
```
|
||||
|
||||
Select `certificate.cer`, *Enter*.
|
||||
|
||||
```
|
||||
[Enroll MOK]
|
||||
|
||||
+------------+
|
||||
| View key 0 |
|
||||
| Continue |
|
||||
+------------+
|
||||
```
|
||||
|
||||
To verify the key contents, select `View key 0`, *Enter*.
|
||||
|
||||
```
|
||||
[Extended Key Usage]
|
||||
OID: Code Signing
|
||||
|
||||
[Serial Number]
|
||||
6B:24:52:E9:3B:84:41:73:B0:22:92:E8:BE:8E:38:85:
|
||||
|
||||
[Issuer]
|
||||
CN=HackBGRT Secure Boot Signer, O=Metabolix
|
||||
|
||||
[Subject]
|
||||
CN=HackBGRT Secure Boot Signer, O=Metabolix
|
||||
|
||||
[Valid Not Before]
|
||||
Nov 9 13:43:56 2023 GMT
|
||||
|
||||
[Valid Not After]
|
||||
Jan 19 03:14:07 2037 GMT
|
||||
|
||||
[Fingerprint]
|
||||
79 8E 64 40 D1 D1 F4 53 30 8D
|
||||
A0 83 A4 77 FE 57 45 30 36 60
|
||||
```
|
||||
|
||||
Press *Enter* to continue.
|
||||
|
||||
```
|
||||
[Enroll MOK]
|
||||
|
||||
+------------+
|
||||
| View key 0 |
|
||||
| Continue |
|
||||
+------------+
|
||||
```
|
||||
|
||||
Select `Continue`, *Enter*.
|
||||
|
||||
```
|
||||
Enroll the key(s)?
|
||||
|
||||
+-----+
|
||||
| No |
|
||||
| Yes |
|
||||
+-----+
|
||||
```
|
||||
|
||||
Select `Yes`, *Enter*.
|
||||
|
||||
```
|
||||
Perform MOK management
|
||||
|
||||
+-----------------------+
|
||||
| Reboot |
|
||||
| Enroll key from disk |
|
||||
| Enroll hash from disk |
|
||||
+-----------------------+
|
||||
```
|
||||
|
||||
Select `Reboot`, *Enter*.
|
||||
|
||||
You are now ready to boot using HackBGRT.
|
||||
|
||||
## Tutorial: *shim* for dummies
|
||||
|
||||
To install *shim* manually, follow these steps (assuming x64 architecture):
|
||||
|
||||
1. Get *shim*, preferably *shim-signed*.
|
||||
2. Rename your boot loader to `grubx64.efi`.
|
||||
3. Copy `shimx64.efi` where your loader used to be.
|
||||
4. Copy `mmx64.efi` to the same folder.
|
||||
|
||||
The *shim* boot process is as follows:
|
||||
|
||||
1. Your computer starts `your-loader-name.efi`, which is now really *shim*.
|
||||
2. Next, *shim* tries to load `grubx64.efi`.
|
||||
3. If `grubx64.efi` is trusted, the boot process continues.
|
||||
4. Otherwise, *shim* offers to launch *MOKManager* `mmx64.efi`, and you can try again after that.
|
||||
551
src/Efi.cs
Normal file
551
src/Efi.cs
Normal file
@@ -0,0 +1,551 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
/**
|
||||
* Methods for handling EFI variables.
|
||||
*/
|
||||
public class Efi {
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern UInt32 GetFirmwareEnvironmentVariableEx(string lpName, string lpGuid, [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, UInt32 nSize, out UInt32 pdwAttributes);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern UInt32 SetFirmwareEnvironmentVariableEx(string lpName, string lpGuid, [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, UInt32 nSize, UInt32 pdwAttributes);
|
||||
|
||||
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
private static extern bool AdjustTokenPrivileges(IntPtr htoken, bool disable, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
|
||||
|
||||
[DllImport("kernel32.dll", ExactSpelling = true)]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
private static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern UInt32 GetSystemFirmwareTable(UInt32 provider, UInt32 table, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, UInt32 len);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private struct TokPriv1Luid {
|
||||
public int Count;
|
||||
public long Luid;
|
||||
public int Attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about an EFI variable.
|
||||
*/
|
||||
public class Variable {
|
||||
public string Name, Guid;
|
||||
public UInt32 Attributes;
|
||||
public byte[] Data;
|
||||
|
||||
/**
|
||||
* Convert to string.
|
||||
*
|
||||
* @return String representation of this object.
|
||||
*/
|
||||
public override string ToString() {
|
||||
if (Data == null) {
|
||||
return $"{Name} Guid={Guid} Attributes={Attributes} Data=null";
|
||||
}
|
||||
var hex = BitConverter.ToString(Data).Replace("-", " ");
|
||||
var text = new string(Data.Select(c => 0x20 <= c && c <= 0x7f ? (char) c : ' ').ToArray());
|
||||
return $"{Name} Guid={Guid} Attributes={Attributes} Text='{text}' Bytes='{hex}'";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public const string EFI_GLOBAL_GUID = "{8be4df61-93ca-11d2-aa0d-00e098032b8c}";
|
||||
|
||||
/**
|
||||
* GUID for HackBGRT EFI variables.
|
||||
*/
|
||||
public const string EFI_HACKBGRT_GUID = "{03c64761-075f-4dba-abfb-2ed89e18b236}";
|
||||
|
||||
/**
|
||||
* Directory containing EFI variables in Linux.
|
||||
*/
|
||||
public const string LinuxEfiDir = "/sys/firmware/efi/efivars";
|
||||
|
||||
/**
|
||||
* Enable the privilege to access EFI variables.
|
||||
*/
|
||||
public static void EnablePrivilege() {
|
||||
if (Directory.Exists(LinuxEfiDir)) {
|
||||
var linuxEfiFile = $"{LinuxEfiDir}/BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c";
|
||||
if (File.Exists(linuxEfiFile)) {
|
||||
using (FileStream fs = File.OpenWrite(linuxEfiFile)) {
|
||||
// OpenWrite throws an exception on error.
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
const int TOKEN_QUERY = 0x00000008;
|
||||
const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
|
||||
const string SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege";
|
||||
IntPtr htoken = IntPtr.Zero;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out htoken)) {
|
||||
throw new Exception("EnablePrivilege: OpenProcessToken failed: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
try {
|
||||
TokPriv1Luid tp;
|
||||
tp.Count = 1;
|
||||
tp.Luid = 0;
|
||||
tp.Attr = SE_PRIVILEGE_ENABLED;
|
||||
if (!LookupPrivilegeValue(null, SE_SYSTEM_ENVIRONMENT_NAME, ref tp.Luid)) {
|
||||
throw new Exception("EnablePrivilege: LookupPrivilegeValue failed: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
if (!AdjustTokenPrivileges(htoken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) {
|
||||
throw new Exception("EnablePrivilege: AdjustTokenPrivileges failed: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
} finally {
|
||||
CloseHandle(htoken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an EFI variable.
|
||||
*
|
||||
* @param name Name of the EFI variable.
|
||||
* @param guid GUID of the EFI variable.
|
||||
* @return Information about the EFI variable.
|
||||
*/
|
||||
private static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
||||
Variable result = new Variable();
|
||||
result.Name = name;
|
||||
result.Guid = guid;
|
||||
result.Data = null;
|
||||
result.Attributes = 0;
|
||||
|
||||
if (Directory.Exists(LinuxEfiDir)) {
|
||||
var linuxEfiFile = $"{LinuxEfiDir}/{name}-{guid.Substring(1, guid.Length - 2)}";
|
||||
if (File.Exists(linuxEfiFile)) {
|
||||
var d = File.ReadAllBytes(linuxEfiFile);
|
||||
result.Attributes = (UInt32)(d[0] + 0x100 * d[1] + 0x10000 * d[2] + 0x1000000 * d[3]);
|
||||
result.Data = d.Skip(4).ToArray();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
for (UInt32 i = 4096; i <= 1024*1024; i *= 2) {
|
||||
byte[] buf = new byte[i];
|
||||
UInt32 len = GetFirmwareEnvironmentVariableEx(name, guid, buf, (UInt32) buf.Length, out result.Attributes);
|
||||
if (len == buf.Length) {
|
||||
continue;
|
||||
}
|
||||
if (len > 0 || Marshal.GetLastWin32Error() == 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());
|
||||
}
|
||||
}
|
||||
throw new Exception("GetFirmwareEnvironmentVariable: too big data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an EFI variable.
|
||||
*
|
||||
* @param v Information of the variable.
|
||||
* @param dryRun Don't actually set the variable.
|
||||
*/
|
||||
private 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(LinuxEfiDir)) {
|
||||
var linuxEfiFile = $"{LinuxEfiDir}/{v.Name}-{v.Guid.Substring(1, v.Guid.Length - 2)}";
|
||||
var a = v.Attributes;
|
||||
var b = new byte[] { (byte) a, (byte) (a >> 8), (byte) (a >> 16), (byte) (a >> 24) };
|
||||
// FIXME: Just writing won't work: File.WriteAllBytes(linuxEfiFile, b.Concat(v.Data).ToArray());
|
||||
Setup.WriteLine("FIXME: Can't yet write EFI variables in Linux.");
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 r = SetFirmwareEnvironmentVariableEx(v.Name, v.Guid, v.Data, (UInt32) v.Data.Length, v.Attributes);
|
||||
if (r == 0) {
|
||||
switch (Marshal.GetLastWin32Error()) {
|
||||
case 87:
|
||||
throw new Exception("SetVariable: Invalid parameter");
|
||||
case 1314:
|
||||
throw new Exception("SetVariable: Privilege not held");
|
||||
default:
|
||||
throw new Exception("SetVariable: error " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Secure Boot is enabled.
|
||||
*
|
||||
* @return 0 for disabled, 1 for enabled, other for unknown.
|
||||
*/
|
||||
public static int GetSecureBootStatus() {
|
||||
// GetVariable("SecureBoot") reports always 1 (on Lenovo E335).
|
||||
// Windows registry seems to work better, though.
|
||||
try {
|
||||
return (int) Registry.GetValue(
|
||||
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
|
||||
"UEFISecureBootEnabled",
|
||||
-1
|
||||
);
|
||||
} catch {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's possible to reboot to EFI setup.
|
||||
*
|
||||
* @return True, if it's possible to reboot to EFI setup.
|
||||
*/
|
||||
public static bool CanBootToFW() {
|
||||
try {
|
||||
Variable tmp = GetVariable("OsIndicationsSupported");
|
||||
return tmp.Data != null && (tmp.Data[0] & 1) != 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that the next reboot should go to EFI setup.
|
||||
*/
|
||||
public static void SetBootToFW() {
|
||||
Variable tmp = GetVariable("OsIndications");
|
||||
if (tmp.Data == null) {
|
||||
tmp.Data = new byte[8];
|
||||
tmp.Attributes = 7;
|
||||
}
|
||||
tmp.Data[0] |= 1;
|
||||
SetVariable(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes into UInt16 values.
|
||||
*
|
||||
* @param bytes The byte array.
|
||||
* @return An enumeration of UInt16 values.
|
||||
*/
|
||||
public static IEnumerable<UInt16> BytesToUInt16s(byte[] bytes) {
|
||||
// TODO: return bytes.Chunk(2).Select(b => (UInt16) (b[0] + 0x100 * b[1])).ToArray();
|
||||
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.
|
||||
*/
|
||||
public static string GetHackBGRTLog() {
|
||||
try {
|
||||
var log = GetVariable("HackBGRTLog", EFI_HACKBGRT_GUID);
|
||||
if (log.Data == null) {
|
||||
return "(null)";
|
||||
}
|
||||
return new string(BytesToUInt16s(log.Data).Select(i => (char)i).ToArray());
|
||||
} catch (Exception e) {
|
||||
return $"Log not found: {e.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the BGRT table (for debugging).
|
||||
*/
|
||||
public static void LogBGRT() {
|
||||
try {
|
||||
const UInt32 acpiBE = 0x41435049, bgrtLE = 0x54524742;
|
||||
UInt32 size = GetSystemFirmwareTable(acpiBE, bgrtLE, null, 0);
|
||||
byte[] buf = new byte[size];
|
||||
var ret = GetSystemFirmwareTable(acpiBE, bgrtLE, buf, size);
|
||||
if (ret == size) {
|
||||
var hex = BitConverter.ToString(buf).Replace("-", " ");
|
||||
Setup.Log($"LogBGRT: {size} bytes: {hex}");
|
||||
} else if (ret == 0) {
|
||||
Setup.Log($"LogBGRT: Win32Error {Marshal.GetLastWin32Error()}");
|
||||
} else {
|
||||
Setup.Log($"LogBGRT: Size problems: spec {0x38}, buf {size}, ret {ret}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Setup.Log($"LogBGRT failed: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/Esp.cs
Normal file
129
src/Esp.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/**
|
||||
* EFI System Partition mounter.
|
||||
*/
|
||||
public sealed class Esp {
|
||||
/** The singleton instance of this class, if ESP is mounted. */
|
||||
private static Esp MountInstance;
|
||||
|
||||
/** EFI System Partition location. */
|
||||
public static string Location { get; private set; }
|
||||
|
||||
/** MS boot loader path on ESP. */
|
||||
public static string MsLoaderPath {
|
||||
get {
|
||||
return Path.Combine(new string[] { Location, "EFI", "Microsoft", "Boot", "bootmgfw.efi"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor: do nothing.
|
||||
*/
|
||||
private Esp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor: unmount.
|
||||
*/
|
||||
~Esp() {
|
||||
if (this == MountInstance) {
|
||||
Unmount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find ESP at a path.
|
||||
*
|
||||
* @param tryPath The new path to try.
|
||||
* @param requireMsLoader Look for MS boot loader specifically?
|
||||
* @return true if the path was given and seems valid, false otherwise.
|
||||
*/
|
||||
public static bool TryPath(string tryPath, bool requireMsLoader = true) {
|
||||
if (MountInstance != null && Location != null) {
|
||||
Unmount();
|
||||
}
|
||||
Location = tryPath;
|
||||
if (Location != null && Location != "") {
|
||||
if (File.Exists(MsLoaderPath)) {
|
||||
Setup.Log($"Esp.TryPath: {Location} has MS boot loader");
|
||||
return true;
|
||||
}
|
||||
if (Directory.Exists(Path.Combine(Location, "EFI"))) {
|
||||
Setup.Log($"Esp.TryPath: {Location} has EFI directory but no loader");
|
||||
if (!requireMsLoader) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Location = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the EFI System Partition, if it's already mounted.
|
||||
*
|
||||
* @return true if the drive was found.
|
||||
*/
|
||||
public static bool Find() {
|
||||
if (MountInstance != null) {
|
||||
return true;
|
||||
}
|
||||
Setup.Log("Esp.Find()");
|
||||
try {
|
||||
// Match "The EFI System Partition is mounted at E:\" with some language support.
|
||||
var re = new Regex(" EFI[^\n]*(?:\n[ \t]*)?([A-Z]:\\\\)");
|
||||
var m = re.Match(Setup.Execute("mountvol", "", false));
|
||||
if (m.Success && TryPath(m.Groups[1].Captures[0].Value)) {
|
||||
return true;
|
||||
}
|
||||
Setup.Log("Esp.Find: no match");
|
||||
} catch (Exception e) {
|
||||
Setup.Log($"Esp.Find: {e.ToString()}");
|
||||
}
|
||||
for (char c = 'A'; c <= 'Z'; ++c) {
|
||||
if (TryPath(c + ":\\")) {
|
||||
Setup.Log($"Esp.Find: found {c}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Setup.Log("Esp.Find: not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the EFI System Partition.
|
||||
*
|
||||
* @return true if the drive was mounted, false otherwise.
|
||||
*/
|
||||
public static bool Mount() {
|
||||
if (MountInstance != null) {
|
||||
return true;
|
||||
}
|
||||
for (char c = 'A'; c <= 'Z'; ++c) {
|
||||
Setup.Log($"Esp.Mount: {c}");
|
||||
if (Setup.Execute("mountvol", c + ": /S", true) != null) {
|
||||
MountInstance = new Esp();
|
||||
if (TryPath(c + ":\\", false)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Exception("Mounted ESP at " + c + ":\\ but it seems to be invalid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmount the EFI System Partition, if necessary.
|
||||
*/
|
||||
private static void Unmount() {
|
||||
if (MountInstance != null) {
|
||||
Setup.Execute("mountvol", Location + " /D", true);
|
||||
Location = null;
|
||||
MountInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
1314
src/Setup.cs
1314
src/Setup.cs
File diff suppressed because it is too large
Load Diff
57
src/config.c
57
src/config.c
@@ -1,14 +1,12 @@
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <efilib.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) {
|
||||
Print(L"HackBGRT: Failed to load configuration (%s)!\n", path);
|
||||
Log(1, L"Failed to load configuration (%s)!\n", path);
|
||||
return FALSE;
|
||||
}
|
||||
CHAR16* str;
|
||||
@@ -21,7 +19,7 @@ BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir,
|
||||
// UTF-8 -> UCS-2
|
||||
EFI_STATUS e = BS->AllocatePool(EfiBootServicesData, data_bytes * 2 + 2, (void**)&str);
|
||||
if (EFI_ERROR(e)) {
|
||||
FreePool(data);
|
||||
BS->FreePool(data);
|
||||
return FALSE;
|
||||
}
|
||||
UINT8* str0 = data;
|
||||
@@ -51,7 +49,7 @@ BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir,
|
||||
}
|
||||
}
|
||||
str[str_len] = 0;
|
||||
FreePool(data);
|
||||
BS->FreePool(data);
|
||||
}
|
||||
|
||||
for (int i = 0; i < str_len;) {
|
||||
@@ -63,42 +61,42 @@ 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.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void SetBMPWithRandom(struct HackBGRT_config* config, int weight, enum HackBGRT_action action, int x, int y, const CHAR16* path) {
|
||||
static void SetBMPWithRandom(struct HackBGRT_config* config, int weight, enum HackBGRT_action action, int x, int y, int o, const CHAR16* path) {
|
||||
config->image_weight_sum += weight;
|
||||
UINT32 random = Random();
|
||||
UINT32 limit = 0xfffffffful / config->image_weight_sum * weight;
|
||||
if (config->debug) {
|
||||
Print(L"HackBGRT: weight %d, action %d, x %d, y %d, path %s, random = %08x, limit = %08x\n", weight, action, x, y, path, random, limit);
|
||||
}
|
||||
if (!config->image_weight_sum || random <= limit) {
|
||||
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);
|
||||
if (random <= limit) {
|
||||
config->action = action;
|
||||
config->image_path = path;
|
||||
config->orientation = o;
|
||||
config->image_x = x;
|
||||
config->image_y = y;
|
||||
}
|
||||
}
|
||||
|
||||
static int ParseCoordinate(const CHAR16* str, enum HackBGRT_action action) {
|
||||
if (str && L'0' <= str[0] && str[0] <= L'9') {
|
||||
return Atoi(str);
|
||||
if (str && ((L'0' <= str[0] && str[0] <= L'9') || str[0] == L'-')) {
|
||||
return str[0] == L'-' ? -(int)Atoi(str+1) : (int)Atoi(str);
|
||||
}
|
||||
if ((str && StrnCmp(str, L"native", 6) == 0) || action == HackBGRT_KEEP) {
|
||||
return HackBGRT_coord_native;
|
||||
if ((str && StrnCmp(str, L"keep", 4) == 0) || action == HackBGRT_KEEP) {
|
||||
return HackBGRT_coord_keep;
|
||||
}
|
||||
return HackBGRT_coord_auto;
|
||||
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) {
|
||||
@@ -110,11 +108,14 @@ static void ReadConfigImage(struct HackBGRT_config* config, const CHAR16* line)
|
||||
} else if (StrStr(line, L"keep")) {
|
||||
action = HackBGRT_KEEP;
|
||||
} else {
|
||||
Print(L"HackBGRT: Invalid image line: %s\n", line);
|
||||
Log(1, L"Invalid image line: %s\n", line);
|
||||
return;
|
||||
}
|
||||
int weight = n && (!f || n < f) ? Atoi(n) : 1;
|
||||
SetBMPWithRandom(config, weight, action, ParseCoordinate(x, action), ParseCoordinate(y, action), f);
|
||||
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);
|
||||
}
|
||||
|
||||
static void ReadConfigResolution(struct HackBGRT_config* config, const CHAR16* line) {
|
||||
@@ -124,11 +125,11 @@ static void ReadConfigResolution(struct HackBGRT_config* config, const CHAR16* l
|
||||
config->resolution_x = *x == '-' ? -(int)Atoi(x+1) : (int)Atoi(x);
|
||||
config->resolution_y = *y == '-' ? -(int)Atoi(y+1) : (int)Atoi(y);
|
||||
} else {
|
||||
Print(L"HackBGRT: Invalid resolution line: %s\n", line);
|
||||
Log(1, L"Invalid resolution line: %s\n", line);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -138,6 +139,10 @@ void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, co
|
||||
config->debug = (StrCmp(line, L"debug=1") == 0);
|
||||
return;
|
||||
}
|
||||
if (StrnCmp(line, L"log=", 4) == 0) {
|
||||
config->log = (StrCmp(line, L"log=1") == 0);
|
||||
return;
|
||||
}
|
||||
if (StrnCmp(line, L"image=", 6) == 0) {
|
||||
ReadConfigImage(config, line + 6);
|
||||
return;
|
||||
@@ -147,12 +152,12 @@ 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) {
|
||||
ReadConfigResolution(config, line + 11);
|
||||
return;
|
||||
}
|
||||
Print(L"Unknown configuration directive: %s\n", line);
|
||||
Log(1, L"Unknown configuration directive: %s\n", line);
|
||||
}
|
||||
|
||||
18
src/config.h
18
src/config.h
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
#include "efi.h"
|
||||
|
||||
/**
|
||||
* Possible actions to perform on the BGRT.
|
||||
@@ -14,22 +14,24 @@ enum HackBGRT_action {
|
||||
* @see struct HackBGRT_config
|
||||
*/
|
||||
enum HackBGRT_coordinate {
|
||||
HackBGRT_coord_auto = 0x10000001,
|
||||
HackBGRT_coord_native = 0x10000002
|
||||
HackBGRT_coord_keep = -1000001
|
||||
};
|
||||
|
||||
/**
|
||||
* The configuration.
|
||||
*/
|
||||
struct HackBGRT_config {
|
||||
int debug;
|
||||
int debug, log;
|
||||
enum HackBGRT_action action;
|
||||
const CHAR16* image_path;
|
||||
int image_x;
|
||||
int image_y;
|
||||
int image_weight_sum;
|
||||
int orientation;
|
||||
int resolution_x;
|
||||
int resolution_y;
|
||||
int old_resolution_x;
|
||||
int old_resolution_y;
|
||||
const CHAR16* boot_path;
|
||||
};
|
||||
|
||||
@@ -37,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);
|
||||
|
||||
159
src/efi.c
Normal file
159
src/efi.c
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "efi.h"
|
||||
#include "util.h"
|
||||
|
||||
// New implementations of some functions in gnu-efi.
|
||||
// These functions are designed to avoid other gnu-efi calls.
|
||||
|
||||
EFI_STATUS LibLocateProtocol(IN EFI_GUID *ProtocolGuid, OUT VOID **Interface) {
|
||||
EFI_HANDLE buffer[256];
|
||||
UINTN size = sizeof(buffer);
|
||||
if (!EFI_ERROR(BS->LocateHandle(ByProtocol, ProtocolGuid, NULL, &size, buffer))) {
|
||||
for (int i = 0; i < size / sizeof(EFI_HANDLE); ++i) {
|
||||
if (!EFI_ERROR(BS->HandleProtocol(buffer[i], ProtocolGuid, Interface))) {
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
return EFI_NOT_FOUND;
|
||||
}
|
||||
|
||||
EFI_DEVICE_PATH *FileDevicePath(IN EFI_HANDLE Device OPTIONAL, IN CHAR16 *FileName) {
|
||||
EFI_DEVICE_PATH *old_path = 0;
|
||||
if (!Device || EFI_ERROR(BS->HandleProtocol(Device, TmpGuidPtr((EFI_GUID) EFI_DEVICE_PATH_PROTOCOL_GUID), (void**)&old_path))) {
|
||||
static EFI_DEVICE_PATH end_path = {END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, {sizeof(end_path), 0}};
|
||||
old_path = &end_path;
|
||||
}
|
||||
UINTN old_path_size = 0, instances = 0;
|
||||
for (EFI_DEVICE_PATH *p0 = old_path;; p0 = NextDevicePathNode(p0)) {
|
||||
old_path_size += DevicePathNodeLength(p0);
|
||||
if (IsDevicePathEndType(p0)) {
|
||||
instances += 1;
|
||||
}
|
||||
if (IsDevicePathEnd(p0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UINTN size_str = (StrLen(FileName) + 1) * sizeof(*FileName);
|
||||
UINTN size_fdp = SIZE_OF_FILEPATH_DEVICE_PATH + size_str;
|
||||
|
||||
EFI_DEVICE_PATH *new_path;
|
||||
if (EFI_ERROR(BS->AllocatePool(EfiBootServicesData, old_path_size + instances * size_fdp, (void**)&new_path))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EFI_DEVICE_PATH *p1 = new_path;
|
||||
for (EFI_DEVICE_PATH *p0 = old_path;; p0 = NextDevicePathNode(p0)) {
|
||||
if (IsDevicePathEndType(p0)) {
|
||||
*p1 = (EFI_DEVICE_PATH) {
|
||||
.Type = MEDIA_DEVICE_PATH,
|
||||
.SubType = MEDIA_FILEPATH_DP,
|
||||
.Length = {size_fdp, size_fdp >> 8},
|
||||
};
|
||||
FILEPATH_DEVICE_PATH *f = (FILEPATH_DEVICE_PATH *) p1;
|
||||
BS->CopyMem(f->PathName, FileName, size_str);
|
||||
p1 = NextDevicePathNode(p1);
|
||||
}
|
||||
BS->CopyMem(p1, p0, DevicePathNodeLength(p0));
|
||||
if (IsDevicePathEnd(p0)) {
|
||||
break;
|
||||
}
|
||||
p1 = NextDevicePathNode(p1);
|
||||
}
|
||||
|
||||
return new_path;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (d[i] != s[i]) {
|
||||
return d[i] - s[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StrnCat(IN CHAR16* dest, IN CONST CHAR16* src, UINTN len) {
|
||||
CHAR16* d = dest;
|
||||
while (*d) {
|
||||
++d;
|
||||
}
|
||||
while (len-- && *src) {
|
||||
*d++ = *src++;
|
||||
}
|
||||
*d = 0;
|
||||
}
|
||||
|
||||
UINTN StrLen(IN CONST CHAR16* s) {
|
||||
UINTN i = 0;
|
||||
while (*s++) {
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
INTN StriCmp(IN CONST CHAR16* s1, IN CONST CHAR16* s2) {
|
||||
while (*s1 && *s2) {
|
||||
CHAR16 c1 = *s1++, c2 = *s2++;
|
||||
if (c1 >= 'A' && c1 <= 'Z') {
|
||||
c1 += 'a' - 'A';
|
||||
}
|
||||
if (c2 >= 'A' && c2 <= 'Z') {
|
||||
c2 += 'a' - 'A';
|
||||
}
|
||||
if (c1 != c2) {
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
return *s1 - *s2;
|
||||
}
|
||||
|
||||
INTN StrnCmp(IN CONST CHAR16* s1, IN CONST CHAR16* s2, UINTN len) {
|
||||
while (*s1 && *s2 && len--) {
|
||||
CHAR16 c1 = *s1++, c2 = *s2++;
|
||||
if (c1 >= 'A' && c1 <= 'Z') {
|
||||
c1 += 'a' - 'A';
|
||||
}
|
||||
if (c2 >= 'A' && c2 <= 'Z') {
|
||||
c2 += 'a' - 'A';
|
||||
}
|
||||
if (c1 != c2) {
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
return len ? *s1 - *s2 : 0;
|
||||
}
|
||||
|
||||
INTN StrCmp(IN CONST CHAR16* s1, IN CONST CHAR16* s2) {
|
||||
while (*s1 && *s2) {
|
||||
if (*s1 != *s2) {
|
||||
return *s1 - *s2;
|
||||
}
|
||||
++s1, ++s2;
|
||||
}
|
||||
return *s1 - *s2;
|
||||
}
|
||||
|
||||
UINTN Atoi(IN CONST CHAR16* s) {
|
||||
UINTN n = 0;
|
||||
while (*s >= '0' && *s <= '9') {
|
||||
n = n * 10 + *s++ - '0';
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void *memset(void *s, int c, __SIZE_TYPE__ n) {
|
||||
unsigned char *p = s;
|
||||
while (n--)
|
||||
*p++ = c;
|
||||
return s;
|
||||
}
|
||||
|
||||
void *memcpy(void *dest, const void *src, __SIZE_TYPE__ n) {
|
||||
const unsigned char *q = src;
|
||||
unsigned char *p = dest;
|
||||
while (n--)
|
||||
*p++ = *q++;
|
||||
return dest;
|
||||
}
|
||||
5
src/efi.h
Normal file
5
src/efi.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
492
src/main.c
492
src/main.c
@@ -1,25 +1,27 @@
|
||||
#include <efi.h>
|
||||
#include <efilib.h>
|
||||
|
||||
#include "efi.h"
|
||||
#include "types.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
|
||||
/**
|
||||
* The Print function signature.
|
||||
* The version.
|
||||
*/
|
||||
typedef UINTN print_t(IN CHAR16 *fmt, ...);
|
||||
#ifdef GIT_DESCRIBE_W
|
||||
const CHAR16 version[] = GIT_DESCRIBE_W;
|
||||
#else
|
||||
const CHAR16 version[] = L"unknown; not an official release?";
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The function for debug printing; either Print or NullPrint.
|
||||
*/
|
||||
print_t* Debug = NullPrint;
|
||||
EFI_SYSTEM_TABLE *ST;
|
||||
EFI_BOOT_SERVICES *BS;
|
||||
EFI_RUNTIME_SERVICES *RT;
|
||||
|
||||
/**
|
||||
* The configuration.
|
||||
*/
|
||||
static struct HackBGRT_config config = {
|
||||
.action = HackBGRT_KEEP
|
||||
.log = 1,
|
||||
.action = HackBGRT_KEEP,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,8 +30,7 @@ static struct HackBGRT_config config = {
|
||||
static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) {
|
||||
static EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
|
||||
if (!gop) {
|
||||
EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
|
||||
LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&gop);
|
||||
LibLocateProtocol(TmpGuidPtr((EFI_GUID) EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID), (void**) &gop);
|
||||
}
|
||||
return gop;
|
||||
}
|
||||
@@ -43,16 +44,22 @@ static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) {
|
||||
static void SetResolution(int w, int h) {
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = GOP();
|
||||
if (!gop) {
|
||||
Debug(L"GOP not found!\n");
|
||||
if (config.resolution_x <= 0 || config.resolution_y <= 0) {
|
||||
config.resolution_x = 1024;
|
||||
config.resolution_y = 768;
|
||||
}
|
||||
config.old_resolution_x = config.resolution_x;
|
||||
config.old_resolution_y = config.resolution_y;
|
||||
Log(config.debug, L"GOP not found! Assuming resolution %dx%d.\n", config.resolution_x, config.resolution_y);
|
||||
return;
|
||||
}
|
||||
UINTN best_i = gop->Mode->Mode;
|
||||
int best_w = gop->Mode->Info->HorizontalResolution;
|
||||
int best_h = gop->Mode->Info->VerticalResolution;
|
||||
w = (w <= 0 ? w < 0 ? best_w : 0x7fffffff : w);
|
||||
h = (h <= 0 ? h < 0 ? best_h : 0x7fffffff : h);
|
||||
int best_w = config.old_resolution_x = gop->Mode->Info->HorizontalResolution;
|
||||
int best_h = config.old_resolution_y = gop->Mode->Info->VerticalResolution;
|
||||
w = (w <= 0 ? w < 0 ? best_w : 999999 : w);
|
||||
h = (h <= 0 ? h < 0 ? best_h : 999999 : h);
|
||||
|
||||
Debug(L"Looking for resolution %dx%d...\n", w, h);
|
||||
Log(config.debug, L"Looking for resolution %dx%d...\n", w, h);
|
||||
for (UINT32 i = gop->Mode->MaxMode; i--;) {
|
||||
int new_w = 0, new_h = 0;
|
||||
|
||||
@@ -62,12 +69,12 @@ static void SetResolution(int w, int h) {
|
||||
continue;
|
||||
}
|
||||
if (info_size < sizeof(*info)) {
|
||||
FreePool(info);
|
||||
BS->FreePool(info);
|
||||
continue;
|
||||
}
|
||||
new_w = info->HorizontalResolution;
|
||||
new_h = info->VerticalResolution;
|
||||
FreePool(info);
|
||||
BS->FreePool(info);
|
||||
|
||||
// Sum of missing w/h should be minimal.
|
||||
int new_missing = max(w - new_w, 0) + max(h - new_h, 0);
|
||||
@@ -85,30 +92,14 @@ static void SetResolution(int w, int h) {
|
||||
best_h = new_h;
|
||||
best_i = i;
|
||||
}
|
||||
Debug(L"Found resolution %dx%d.\n", best_w, best_h);
|
||||
Log(config.debug, L"Found resolution %dx%d.\n", best_w, best_h);
|
||||
config.resolution_x = best_w;
|
||||
config.resolution_y = best_h;
|
||||
if (best_i != gop->Mode->Mode) {
|
||||
gop->SetMode(gop, best_i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the correct coordinate (manual, automatic, native)
|
||||
*
|
||||
* @param value The configured coordinate value; has special values for automatic and native.
|
||||
* @param automatic The automatically calculated alternative.
|
||||
* @param native The original coordinate.
|
||||
* @see enum HackBGRT_coordinate
|
||||
*/
|
||||
static int SelectCoordinate(int value, int automatic, int native) {
|
||||
if (value == HackBGRT_coord_auto) {
|
||||
return automatic;
|
||||
}
|
||||
if (value == HackBGRT_coord_native) {
|
||||
return native;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new XSDT with the given number of entries.
|
||||
*
|
||||
@@ -121,11 +112,11 @@ ACPI_SDT_HEADER* CreateXsdt(ACPI_SDT_HEADER* xsdt0, UINTN entries) {
|
||||
UINT32 xsdt_len = sizeof(ACPI_SDT_HEADER) + entries * sizeof(UINT64);
|
||||
BS->AllocatePool(EfiACPIReclaimMemory, xsdt_len, (void**)&xsdt);
|
||||
if (!xsdt) {
|
||||
Print(L"HackBGRT: Failed to allocate memory for XSDT.\n");
|
||||
Log(1, L"Failed to allocate memory for XSDT.\n");
|
||||
return 0;
|
||||
}
|
||||
ZeroMem(xsdt, xsdt_len);
|
||||
CopyMem(xsdt, xsdt0, min(xsdt0->length, xsdt_len));
|
||||
BS->SetMem(xsdt, xsdt_len, 0);
|
||||
BS->CopyMem(xsdt, xsdt0, min(xsdt0->length, xsdt_len));
|
||||
xsdt->length = xsdt_len;
|
||||
SetAcpiSdtChecksum(xsdt);
|
||||
return xsdt;
|
||||
@@ -144,26 +135,25 @@ ACPI_SDT_HEADER* CreateXsdt(ACPI_SDT_HEADER* xsdt0, UINTN entries) {
|
||||
*/
|
||||
static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt) {
|
||||
for (int i = 0; i < ST->NumberOfTableEntries; i++) {
|
||||
EFI_GUID Acpi20TableGuid = ACPI_20_TABLE_GUID;
|
||||
EFI_GUID* vendor_guid = &ST->ConfigurationTable[i].VendorGuid;
|
||||
if (!CompareGuid(vendor_guid, &AcpiTableGuid) && !CompareGuid(vendor_guid, &Acpi20TableGuid)) {
|
||||
if (CompareMem(vendor_guid, TmpGuidPtr((EFI_GUID) ACPI_TABLE_GUID), sizeof(EFI_GUID)) != 0 && CompareMem(vendor_guid, TmpGuidPtr((EFI_GUID) ACPI_20_TABLE_GUID), sizeof(EFI_GUID)) != 0) {
|
||||
continue;
|
||||
}
|
||||
ACPI_20_RSDP* rsdp = (ACPI_20_RSDP *) ST->ConfigurationTable[i].VendorTable;
|
||||
if (CompareMem(rsdp->signature, "RSD PTR ", 8) != 0 || rsdp->revision < 2 || !VerifyAcpiRsdp2Checksums(rsdp)) {
|
||||
continue;
|
||||
}
|
||||
Debug(L"RSDP: revision = %d, OEM ID = %s\n", rsdp->revision, TmpStr(rsdp->oem_id, 6));
|
||||
Log(config.debug, L"RSDP @%x: revision = %d, OEM ID = %s\n", (UINTN)rsdp, rsdp->revision, TmpStr(rsdp->oem_id, 6));
|
||||
|
||||
ACPI_SDT_HEADER* xsdt = (ACPI_SDT_HEADER *) (UINTN) rsdp->xsdt_address;
|
||||
if (!xsdt || CompareMem(xsdt->signature, "XSDT", 4) != 0 || !VerifyAcpiSdtChecksum(xsdt)) {
|
||||
Debug(L"* XSDT: missing or invalid\n");
|
||||
Log(config.debug, L"* XSDT: missing or invalid\n");
|
||||
continue;
|
||||
}
|
||||
UINT64* entry_arr = (UINT64*)&xsdt[1];
|
||||
UINT32 entry_arr_length = (xsdt->length - sizeof(*xsdt)) / sizeof(UINT64);
|
||||
|
||||
Debug(L"* XSDT: OEM ID = %s, entry count = %d\n", TmpStr(xsdt->oem_id, 6), entry_arr_length);
|
||||
Log(config.debug, L"* XSDT @%x: OEM ID = %s, entry count = %d\n", (UINTN)xsdt, TmpStr(xsdt->oem_id, 6), entry_arr_length);
|
||||
|
||||
int bgrt_count = 0;
|
||||
for (int j = 0; j < entry_arr_length; j++) {
|
||||
@@ -171,16 +161,16 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
if (CompareMem(entry->signature, "BGRT", 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
Debug(L" - ACPI table: %s, revision = %d, OEM ID = %s\n", TmpStr(entry->signature, 4), entry->revision, TmpStr(entry->oem_id, 6));
|
||||
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:
|
||||
if (!bgrt) {
|
||||
Debug(L" -> Returning this one for later use.\n");
|
||||
Log(config.debug, L" -> Returning this one for later use.\n");
|
||||
bgrt = (ACPI_BGRT*) entry;
|
||||
}
|
||||
break;
|
||||
case HackBGRT_REMOVE:
|
||||
Debug(L" -> Deleting.\n");
|
||||
Log(config.debug, L" -> Deleting.\n");
|
||||
for (int k = j+1; k < entry_arr_length; ++k) {
|
||||
entry_arr[k-1] = entry_arr[k];
|
||||
}
|
||||
@@ -190,13 +180,13 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
--j;
|
||||
break;
|
||||
case HackBGRT_REPLACE:
|
||||
Debug(L" -> Replacing.\n");
|
||||
Log(config.debug, L" -> Replacing.\n");
|
||||
entry_arr[j] = (UINTN) bgrt;
|
||||
}
|
||||
bgrt_count += 1;
|
||||
}
|
||||
if (!bgrt_count && action == HackBGRT_REPLACE && bgrt) {
|
||||
Debug(L" - Adding missing BGRT.\n");
|
||||
Log(config.debug, L" - Adding missing BGRT.\n");
|
||||
xsdt = CreateXsdt(xsdt, entry_arr_length + 1);
|
||||
entry_arr = (UINT64*)&xsdt[1];
|
||||
entry_arr[entry_arr_length++] = (UINTN) bgrt;
|
||||
@@ -209,47 +199,109 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a bitmap or generate a black one.
|
||||
* Generate a BMP with the given size and color.
|
||||
*
|
||||
* @param root_dir The root directory for loading a BMP.
|
||||
* @param path The BMP path within the root directory; NULL for a black BMP.
|
||||
* @return The loaded BMP, or 0 if not available.
|
||||
* @param w The width.
|
||||
* @param h The height.
|
||||
* @param r The red component.
|
||||
* @param g The green component.
|
||||
* @param b The blue component.
|
||||
* @return The generated BMP, or 0 on failure.
|
||||
*/
|
||||
static BMP* LoadBMP(EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
static BMP* MakeBMP(int w, int h, UINT8 r, UINT8 g, UINT8 b) {
|
||||
BMP* bmp = 0;
|
||||
if (!path) {
|
||||
BS->AllocatePool(EfiBootServicesData, 58, (void**) &bmp);
|
||||
if (!bmp) {
|
||||
Print(L"HackBGRT: Failed to allocate a blank BMP!\n");
|
||||
BS->Stall(1000000);
|
||||
return 0;
|
||||
}
|
||||
CopyMem(
|
||||
bmp,
|
||||
"\x42\x4d\x3a\x00\x00\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00"
|
||||
"\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x18\x00\x00\x00"
|
||||
"\x00\x00\x04\x00\x00\x00\x13\x0b\x00\x00\x13\x0b\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
58
|
||||
);
|
||||
return bmp;
|
||||
}
|
||||
Debug(L"HackBGRT: Loading %s.\n", path);
|
||||
bmp = LoadFile(root_dir, path, 0);
|
||||
BS->AllocatePool(EfiBootServicesData, 54 + w * h * 4, (void**) &bmp);
|
||||
if (!bmp) {
|
||||
Print(L"HackBGRT: Failed to load BMP (%s)!\n", path);
|
||||
Log(1, L"Failed to allocate a blank BMP!\n");
|
||||
BS->Stall(1000000);
|
||||
return 0;
|
||||
}
|
||||
*bmp = (BMP) {
|
||||
.magic_BM = { 'B', 'M' },
|
||||
.file_size = 54 + w * h * 4,
|
||||
.pixel_data_offset = 54,
|
||||
.dib_header_size = 40,
|
||||
.width = w,
|
||||
.height = h,
|
||||
.planes = 1,
|
||||
.bpp = 32,
|
||||
};
|
||||
UINT8* data = (UINT8*) bmp + bmp->pixel_data_offset;
|
||||
for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) {
|
||||
*data++ = b;
|
||||
*data++ = g;
|
||||
*data++ = r;
|
||||
*data++ = 0;
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a bitmap or generate a black one.
|
||||
*
|
||||
* @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 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(base_dir, path, &size);
|
||||
if (bmp) {
|
||||
if (size >= bmp->file_size
|
||||
&& CompareMem(bmp, "BM", 2) == 0
|
||||
&& bmp->file_size > bmp->pixel_data_offset
|
||||
&& bmp->width > 0
|
||||
&& bmp->height > 0
|
||||
&& (bmp->bpp == 32 || bmp->bpp == 24)
|
||||
&& bmp->height * (-(-(bmp->width * (bmp->bpp / 8)) & ~3)) <= bmp->file_size - bmp->pixel_data_offset
|
||||
&& bmp->compression == 0) {
|
||||
return bmp;
|
||||
}
|
||||
BS->FreePool(bmp);
|
||||
Log(1, L"Invalid BMP (%s)!\n", path);
|
||||
} else {
|
||||
Log(1, L"Failed to load BMP (%s)!\n", path);
|
||||
}
|
||||
BS->Stall(1000000);
|
||||
return MakeBMP(16, 16, 255, 0, 0); // error = red image
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop a BMP to the given size.
|
||||
*
|
||||
* @param bmp The BMP to crop.
|
||||
* @param w The maximum width.
|
||||
* @param h The maximum height.
|
||||
*/
|
||||
static void CropBMP(BMP* bmp, int w, int h) {
|
||||
const int old_pitch = -(-(bmp->width * (bmp->bpp / 8)) & ~3);
|
||||
bmp->image_size = 0;
|
||||
bmp->width = min(bmp->width, w);
|
||||
bmp->height = min(bmp->height, h);
|
||||
const int new_pitch = -(-(bmp->width * (bmp->bpp / 8)) & ~3);
|
||||
|
||||
if (new_pitch < old_pitch) {
|
||||
for (int i = 1; i < bmp->height; ++i) {
|
||||
BS->CopyMem(
|
||||
(UINT8*) bmp + bmp->pixel_data_offset + i * new_pitch,
|
||||
(UINT8*) bmp + bmp->pixel_data_offset + i * old_pitch,
|
||||
new_pitch
|
||||
);
|
||||
}
|
||||
}
|
||||
bmp->file_size = bmp->pixel_data_offset + bmp->height * new_pitch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -259,14 +311,15 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
// KEEP/REPLACE: first get the old BGRT entry.
|
||||
ACPI_BGRT* bgrt = HandleAcpiTables(HackBGRT_KEEP, 0);
|
||||
|
||||
// Get the old BMP and position, if possible.
|
||||
BMP* old_bmp = 0;
|
||||
int old_x = 0, old_y = 0;
|
||||
if (bgrt && VerifyAcpiSdtChecksum(bgrt)) {
|
||||
old_bmp = (BMP*) (UINTN) bgrt->image_address;
|
||||
old_x = bgrt->image_offset_x;
|
||||
old_y = bgrt->image_offset_y;
|
||||
}
|
||||
// Get the old BMP and position (relative to screen center), if possible.
|
||||
const int old_valid = bgrt && VerifyAcpiSdtChecksum(bgrt);
|
||||
BMP* old_bmp = old_valid ? (BMP*) (UINTN) bgrt->image_address : 0;
|
||||
const int old_orientation = old_valid ? ((bgrt->status >> 1) & 3) : 0;
|
||||
const int old_swap = old_orientation & 1;
|
||||
const int old_reso_x = old_swap ? config.old_resolution_y : config.old_resolution_x;
|
||||
const int old_reso_y = old_swap ? config.old_resolution_x : config.old_resolution_y;
|
||||
const int old_x = old_bmp ? bgrt->image_offset_x + (old_bmp->width - old_reso_x) / 2 : 0;
|
||||
const int old_y = old_bmp ? bgrt->image_offset_y + (old_bmp->height - old_reso_y) / 2 : 0;
|
||||
|
||||
// Missing BGRT?
|
||||
if (!bgrt) {
|
||||
@@ -277,22 +330,29 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
// Replace missing = allocate new.
|
||||
BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*bgrt), (void**)&bgrt);
|
||||
if (!bgrt) {
|
||||
Print(L"HackBGRT: Failed to allocate memory for BGRT.\n");
|
||||
Log(1, L"Failed to allocate memory for BGRT.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the BGRT.
|
||||
const char data[0x38] =
|
||||
"BGRT" "\x38\x00\x00\x00" "\x00" "\xd6" "Mtblx*" "HackBGRT"
|
||||
"\x20\x17\x00\x00" "PTL " "\x02\x00\x00\x00"
|
||||
"\x01\x00" "\x00" "\x00";
|
||||
CopyMem(bgrt, data, sizeof(data));
|
||||
*bgrt = (ACPI_BGRT) {
|
||||
.header = {
|
||||
.signature = "BGRT",
|
||||
.length = sizeof(*bgrt),
|
||||
.revision = 1,
|
||||
.oem_id = "Mtblx*",
|
||||
.oem_table_id = "HackBGRT",
|
||||
.oem_revision = 1,
|
||||
.asl_compiler_id = *(const UINT32*) "None",
|
||||
.asl_compiler_revision = 1,
|
||||
},
|
||||
.version = 1,
|
||||
};
|
||||
|
||||
// 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.
|
||||
@@ -301,22 +361,33 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Crop the image to screen.
|
||||
CropBMP(new_bmp, config.resolution_x, config.resolution_y);
|
||||
|
||||
// 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);
|
||||
bgrt->status = new_orientation << 1;
|
||||
|
||||
// Calculate the automatically centered position for the image.
|
||||
int auto_x = 0, auto_y = 0;
|
||||
if (GOP()) {
|
||||
auto_x = max(0, ((int)GOP()->Mode->Info->HorizontalResolution - (int)new_bmp->width) / 2);
|
||||
auto_y = max(0, ((int)GOP()->Mode->Info->VerticalResolution * 2/3 - (int)new_bmp->height) / 2);
|
||||
} else if (old_bmp) {
|
||||
auto_x = max(0, old_x + ((int)old_bmp->width - (int)new_bmp->width) / 2);
|
||||
auto_y = max(0, old_y + ((int)old_bmp->height - (int)new_bmp->height) / 2);
|
||||
}
|
||||
// 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;
|
||||
|
||||
// Set the position (manual, automatic, original).
|
||||
bgrt->image_offset_x = SelectCoordinate(config.image_x, auto_x, old_x);
|
||||
bgrt->image_offset_y = SelectCoordinate(config.image_y, auto_y, old_y);
|
||||
Debug(L"HackBGRT: BMP at (%d, %d).\n", (int) bgrt->image_offset_x, (int) bgrt->image_offset_y);
|
||||
// Calculate absolute position.
|
||||
const int max_x = new_reso_x - new_bmp->width;
|
||||
const int max_y = new_reso_y - new_bmp->height;
|
||||
bgrt->image_offset_x = max(0, min(max_x, new_x + (new_reso_x - new_bmp->width) / 2));
|
||||
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",
|
||||
(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);
|
||||
@@ -324,94 +395,131 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The main program.
|
||||
* Load an application.
|
||||
*/
|
||||
EFI_STATUS EFIAPI EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
InitializeLib(image_handle, ST_);
|
||||
|
||||
EFI_LOADED_IMAGE* image;
|
||||
if (EFI_ERROR(BS->HandleProtocol(image_handle, &LoadedImageProtocol, (void**) &image))) {
|
||||
Debug(L"HackBGRT: LOADED_IMAGE_PROTOCOL failed.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
EFI_FILE_HANDLE root_dir = LibOpenRoot(image->DeviceHandle);
|
||||
|
||||
CHAR16 **argv;
|
||||
int argc = GetShellArgcArgv(image_handle, &argv);
|
||||
|
||||
if (argc <= 1) {
|
||||
const CHAR16* config_path = L"\\EFI\\HackBGRT\\config.txt";
|
||||
if (!ReadConfigFile(&config, root_dir, config_path)) {
|
||||
Print(L"HackBGRT: No config, no command line!\n", config_path);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
ReadConfigLine(&config, root_dir, argv[i]);
|
||||
}
|
||||
Debug = config.debug ? Print : NullPrint;
|
||||
|
||||
SetResolution(config.resolution_x, config.resolution_y);
|
||||
HackBgrt(root_dir);
|
||||
|
||||
EFI_HANDLE next_image_handle = 0;
|
||||
if (!config.boot_path) {
|
||||
Print(L"HackBGRT: Boot path not specified.\n");
|
||||
} else {
|
||||
Debug(L"HackBGRT: Loading application %s.\n", config.boot_path);
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, (CHAR16*) config.boot_path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &next_image_handle))) {
|
||||
Print(L"HackBGRT: Failed to load application %s.\n", config.boot_path);
|
||||
}
|
||||
}
|
||||
if (!next_image_handle) {
|
||||
static CHAR16 default_boot_path[] = L"\\EFI\\HackBGRT\\bootmgfw-original.efi";
|
||||
Debug(L"HackBGRT: Loading application %s.\n", default_boot_path);
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, default_boot_path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &next_image_handle))) {
|
||||
Print(L"HackBGRT: Also failed to load application %s.\n", default_boot_path);
|
||||
goto fail;
|
||||
}
|
||||
Print(L"HackBGRT: Reverting to %s.\n", default_boot_path);
|
||||
Print(L"Press escape to cancel, any other key to boot.\n");
|
||||
if (ReadKey().ScanCode == SCAN_ESC) {
|
||||
goto fail;
|
||||
}
|
||||
config.boot_path = default_boot_path;
|
||||
}
|
||||
if (config.debug) {
|
||||
Print(L"HackBGRT: Ready to boot.\nPress escape to cancel, any other key to boot.\n");
|
||||
if (ReadKey().ScanCode == SCAN_ESC) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (EFI_ERROR(BS->StartImage(next_image_handle, 0, 0))) {
|
||||
Print(L"HackBGRT: Failed to start %s.\n", config.boot_path);
|
||||
goto fail;
|
||||
}
|
||||
Print(L"HackBGRT: Started %s. Why are we still here?!\n", config.boot_path);
|
||||
goto fail;
|
||||
|
||||
fail: {
|
||||
Print(L"HackBGRT has failed. Use parameter debug=1 for details.\n");
|
||||
Print(L"Get a Windows install disk or a recovery disk to fix your boot.\n");
|
||||
#ifdef GIT_DESCRIBE
|
||||
Print(L"HackBGRT version: " GIT_DESCRIBE L"\n");
|
||||
#else
|
||||
Print(L"HackBGRT version: unknown; not an official release?\n");
|
||||
#endif
|
||||
Print(L"Press any key to exit.\n");
|
||||
ReadKey();
|
||||
return 1;
|
||||
static EFI_HANDLE LoadApp(int print_failure, EFI_HANDLE image_handle, EFI_LOADED_IMAGE* image, const CHAR16* path) {
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, (CHAR16*) path);
|
||||
EFI_HANDLE result = 0;
|
||||
Log(config.debug, L"Loading application %s.\n", path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &result))) {
|
||||
Log(config.debug || print_failure, L"Failed to load application %s.\n", path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward to EfiMain.
|
||||
*
|
||||
* Some compilers and architectures differ in underscore handling. This helps.
|
||||
* The main program.
|
||||
*/
|
||||
EFI_STATUS EFIAPI _EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
return EfiMain(image_handle, ST_);
|
||||
EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
ST = 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;
|
||||
if (EFI_ERROR(BS->HandleProtocol(image_handle, TmpGuidPtr((EFI_GUID) EFI_LOADED_IMAGE_PROTOCOL_GUID), (void**) &image))) {
|
||||
Log(config.debug, L"LOADED_IMAGE_PROTOCOL failed.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
EFI_FILE_IO_INTERFACE* io;
|
||||
if (EFI_ERROR(BS->HandleProtocol(image->DeviceHandle, TmpGuidPtr((EFI_GUID) EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID), (void**) &io))) {
|
||||
Log(config.debug, L"FILE_SYSTEM_PROTOCOL failed.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
EFI_FILE_HANDLE root_dir;
|
||||
if (EFI_ERROR(io->OpenVolume(io, &root_dir))) {
|
||||
Log(config.debug, L"Failed to open root directory.\n");
|
||||
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;
|
||||
}
|
||||
|
||||
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";
|
||||
if (!ReadConfigFile(&config, base_dir, config_path)) {
|
||||
Log(1, L"No config, no command line!\n", config_path);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
CHAR16 **argv = shell_param_proto->Argv;
|
||||
int argc = shell_param_proto->Argc;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
ReadConfigLine(&config, base_dir, argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.debug) {
|
||||
Log(-1, L"HackBGRT version: %s\n", version);
|
||||
}
|
||||
|
||||
SetResolution(config.resolution_x, config.resolution_y);
|
||||
HackBgrt(base_dir);
|
||||
|
||||
EFI_HANDLE next_image_handle = 0;
|
||||
static CHAR16 backup_boot_path[] = L"\\EFI\\HackBGRT\\bootmgfw-original.efi";
|
||||
static CHAR16 ms_boot_path[] = L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
|
||||
int try_ms_quietly = 1;
|
||||
|
||||
if (config.boot_path && StriCmp(config.boot_path, L"MS") != 0) {
|
||||
next_image_handle = LoadApp(1, image_handle, image, config.boot_path);
|
||||
try_ms_quietly = 0;
|
||||
}
|
||||
if (!next_image_handle) {
|
||||
config.boot_path = backup_boot_path;
|
||||
next_image_handle = LoadApp(!try_ms_quietly, image_handle, image, config.boot_path);
|
||||
if (!next_image_handle) {
|
||||
config.boot_path = ms_boot_path;
|
||||
next_image_handle = LoadApp(!try_ms_quietly, image_handle, image, config.boot_path);
|
||||
if (!next_image_handle) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if (try_ms_quietly) {
|
||||
goto ready_to_boot;
|
||||
}
|
||||
Log(1, L"Reverting to %s.\n", config.boot_path);
|
||||
Log(-1, L"Press escape to cancel or any other key (or wait 15 seconds) to boot.\n");
|
||||
if (ReadKey(15000).ScanCode == SCAN_ESC) {
|
||||
goto fail;
|
||||
}
|
||||
} else ready_to_boot: if (config.debug) {
|
||||
Log(-1, L"Ready to boot.\n");
|
||||
Log(-1, L"If all goes well, you can set debug=0 and log=0 in config.txt.\n");
|
||||
Log(-1, L"Press escape to cancel or any other key (or wait 15 seconds) to boot.\n");
|
||||
if (ReadKey(15000).ScanCode == SCAN_ESC) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!config.log) {
|
||||
ClearLogVariable();
|
||||
}
|
||||
if (EFI_ERROR(BS->StartImage(next_image_handle, 0, 0))) {
|
||||
Log(1, L"Failed to start %s.\n", config.boot_path);
|
||||
goto fail;
|
||||
}
|
||||
Log(1, L"Started %s. Why are we still here?!\n", config.boot_path);
|
||||
Log(-1, L"Please check that %s is not actually HackBGRT!\n", config.boot_path);
|
||||
goto fail;
|
||||
|
||||
fail: {
|
||||
Log(1, L"HackBGRT has failed.\n");
|
||||
Log(-1, L"Dumping log:\n\n");
|
||||
DumpLog();
|
||||
Log(-1, L"If you can't boot into Windows, get install/recovery disk to fix your boot.\n");
|
||||
Log(-1, L"Press any key (or wait 15 seconds) to exit.\n");
|
||||
ReadKey(15000);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
10
src/sbat.c
Normal file
10
src/sbat.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifdef GIT_DESCRIBE
|
||||
#define SBAT_READABLE_VERSION GIT_DESCRIBE
|
||||
#else
|
||||
#define SBAT_READABLE_VERSION "unknown"
|
||||
#endif
|
||||
|
||||
const char sbat[] __attribute__ ((section (".sbat"))) =
|
||||
"sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md\n"
|
||||
"hackbgrt,1,Metabolix,HackBGRT," SBAT_READABLE_VERSION ",https://github.com/Metabolix/HackBGRT\n"
|
||||
;
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
#include "efi.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
@@ -52,6 +52,12 @@ typedef struct {
|
||||
UINT32 height;
|
||||
UINT16 planes;
|
||||
UINT16 bpp;
|
||||
UINT32 compression;
|
||||
UINT32 image_size;
|
||||
UINT32 x_pixels_per_meter;
|
||||
UINT32 y_pixels_per_meter;
|
||||
UINT32 colors_used;
|
||||
UINT32 important_colors;
|
||||
} BMP;
|
||||
|
||||
/**
|
||||
|
||||
129
src/util.c
129
src/util.c
@@ -1,7 +1,5 @@
|
||||
#include "util.h"
|
||||
|
||||
#include <efilib.h>
|
||||
|
||||
const CHAR16* TmpStr(CHAR8 *src, int length) {
|
||||
static CHAR16 arr[4][16];
|
||||
static int j;
|
||||
@@ -14,8 +12,99 @@ const CHAR16* TmpStr(CHAR8 *src, int length) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
UINTN NullPrint(IN CHAR16 *fmt, ...) {
|
||||
return 0;
|
||||
const CHAR16* TmpIntToStr(UINT32 x) {
|
||||
static CHAR16 buf[20];
|
||||
int i = 20 - 1;
|
||||
buf[i] = 0;
|
||||
if (!x) {
|
||||
buf[--i] = '0';
|
||||
}
|
||||
while (x && i) {
|
||||
buf[--i] = '0' + (x % 10);
|
||||
x /= 10;
|
||||
}
|
||||
return &buf[i];
|
||||
}
|
||||
|
||||
#define log_buffer_size (65536)
|
||||
CHAR16 log_buffer[log_buffer_size] = {0};
|
||||
|
||||
CHAR16 LogVarName[] = L"HackBGRTLog";
|
||||
EFI_GUID LogVarGuid = {0x03c64761, 0x075f, 0x4dba, {0xab, 0xfb, 0x2e, 0xd8, 0x9e, 0x18, 0xb2, 0x36}}; // self-made: 03c64761-075f-4dba-abfb-2ed89e18b236
|
||||
|
||||
void Log(int mode, IN CONST CHAR16 *fmt, ...) {
|
||||
va_list args;
|
||||
CHAR16 buf[256] = {0};
|
||||
int buf_i = 0;
|
||||
#define putchar(c) { if (buf_i < 255) { buf[buf_i++] = c; } }
|
||||
va_start(args, fmt);
|
||||
for (int i = 0; fmt[i]; ++i) {
|
||||
if (fmt[i] == '\n') {
|
||||
putchar('\r');
|
||||
putchar('\n');
|
||||
continue;
|
||||
}
|
||||
if (fmt[i] != '%') {
|
||||
putchar(fmt[i]);
|
||||
continue;
|
||||
}
|
||||
++i;
|
||||
switch (fmt[i]) {
|
||||
case '%': putchar('%'); continue;
|
||||
case 'd': goto fmt_decimal;
|
||||
case 'x': goto fmt_hex;
|
||||
case 's': goto fmt_string;
|
||||
case 0: goto fmt_end;
|
||||
}
|
||||
putchar('%');
|
||||
putchar(fmt[i]);
|
||||
continue;
|
||||
|
||||
if (0) fmt_decimal: {
|
||||
INT32 x = va_arg(args, INT32);
|
||||
if (x < 0) {
|
||||
putchar('-');
|
||||
x = -x;
|
||||
}
|
||||
const CHAR16* s = TmpIntToStr(x);
|
||||
while (*s) {
|
||||
putchar(*s++);
|
||||
}
|
||||
}
|
||||
if (0) fmt_hex: {
|
||||
UINT32 x = va_arg(args, UINT32);
|
||||
for (int pos = 8, started = 0; pos--;) {
|
||||
int d = (x >> (4 * pos)) & 0xf;
|
||||
if (d || started || pos == 0) {
|
||||
putchar("0123456789abcdef"[d]);
|
||||
started = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (0) fmt_string: {
|
||||
CHAR16* s = va_arg(args, CHAR16*);
|
||||
while (*s) {
|
||||
putchar(*s++);
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt_end:
|
||||
va_end(args);
|
||||
if (mode) {
|
||||
ST->ConOut->OutputString(ST->ConOut, buf);
|
||||
}
|
||||
if (mode != -1) {
|
||||
StrnCat(log_buffer, buf, log_buffer_size - StrLen(log_buffer) - 1);
|
||||
RT->SetVariable(LogVarName, &LogVarGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, StrLen(log_buffer) * 2, log_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void DumpLog(void) {
|
||||
ST->ConOut->OutputString(ST->ConOut, log_buffer);
|
||||
}
|
||||
|
||||
void ClearLogVariable(void) {
|
||||
RT->SetVariable(LogVarName, &LogVarGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, 0, 0);
|
||||
}
|
||||
|
||||
const CHAR16* TrimLeft(const CHAR16* s) {
|
||||
@@ -68,14 +157,28 @@ void RandomSeedAuto(void) {
|
||||
RandomSeed(a, b), Random(), Random();
|
||||
}
|
||||
|
||||
void WaitKey(void) {
|
||||
EFI_STATUS WaitKey(UINT64 timeout_ms) {
|
||||
ST->ConIn->Reset(ST->ConIn, FALSE);
|
||||
WaitForSingleEvent(ST->ConIn->WaitForKey, 0);
|
||||
const int ms_to_100ns = 10000;
|
||||
|
||||
EFI_EVENT events[2] = {ST->ConIn->WaitForKey};
|
||||
EFI_STATUS status = BS->CreateEvent(EVT_TIMER, 0, NULL, NULL, &events[1]);
|
||||
if (!EFI_ERROR(status)) {
|
||||
BS->SetTimer(events[1], TimerRelative, timeout_ms * ms_to_100ns);
|
||||
UINTN index;
|
||||
status = BS->WaitForEvent(2, events, &index);
|
||||
BS->CloseEvent(events[1]);
|
||||
if (!EFI_ERROR(status) && index == 1) {
|
||||
status = EFI_TIMEOUT;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
EFI_INPUT_KEY ReadKey(void) {
|
||||
WaitKey();
|
||||
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;
|
||||
}
|
||||
@@ -89,9 +192,11 @@ void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_p
|
||||
return 0;
|
||||
}
|
||||
|
||||
EFI_FILE_INFO *info = LibFileInfo(handle);
|
||||
UINTN size = info->FileSize;
|
||||
FreePool(info);
|
||||
UINT64 get_size = 0;
|
||||
handle->SetPosition(handle, ~(UINT64)0);
|
||||
handle->GetPosition(handle, &get_size);
|
||||
handle->SetPosition(handle, 0);
|
||||
UINTN size = (UINTN) get_size;
|
||||
|
||||
void* data = 0;
|
||||
e = BS->AllocatePool(EfiBootServicesData, size + padding, &data);
|
||||
@@ -105,7 +210,7 @@ void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_p
|
||||
}
|
||||
handle->Close(handle);
|
||||
if (EFI_ERROR(e)) {
|
||||
FreePool(data);
|
||||
BS->FreePool(data);
|
||||
return 0;
|
||||
}
|
||||
if (size_ptr) {
|
||||
|
||||
34
src/util.h
34
src/util.h
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
#include "efi.h"
|
||||
|
||||
/**
|
||||
* Convert a short ASCII string to UCS2, store in a static array.
|
||||
@@ -12,9 +12,22 @@
|
||||
extern const CHAR16* TmpStr(CHAR8 *src, int length);
|
||||
|
||||
/**
|
||||
* Empty function that has the same signature as Print.
|
||||
* Print or log a string.
|
||||
*
|
||||
* @param mode -1 = print without logging, 0 = no, 1 = yes.
|
||||
* @param fmt The format string. Supports %d, %x, %s.
|
||||
*/
|
||||
extern UINTN NullPrint(IN CHAR16 *fmt, ...);
|
||||
extern void Log(int mode, IN CONST CHAR16 *fmt, ...);
|
||||
|
||||
/**
|
||||
* Dump the log buffer to the screen.
|
||||
*/
|
||||
extern void DumpLog(void);
|
||||
|
||||
/**
|
||||
* Clear the log EFI variable, for minor RAM savings.
|
||||
*/
|
||||
extern void ClearLogVariable(void);
|
||||
|
||||
/**
|
||||
* Return the greater of two numbers.
|
||||
@@ -82,15 +95,18 @@ extern void RandomSeedAuto(void);
|
||||
|
||||
/**
|
||||
* Wait for a key press. It will still remain in the buffer.
|
||||
*
|
||||
* @param timeout_ms The timeout in milliseconds, or 0 for no timeout.
|
||||
*/
|
||||
extern void WaitKey(void);
|
||||
extern EFI_STATUS WaitKey(UINT64 timeout_ms);
|
||||
|
||||
/**
|
||||
* Wait for a key press and read it.
|
||||
*
|
||||
* @param timeout_ms The timeout in milliseconds, or 0 for no timeout.
|
||||
* @return The pressed key.
|
||||
*/
|
||||
extern EFI_INPUT_KEY ReadKey(void);
|
||||
extern EFI_INPUT_KEY ReadKey(UINT64 timeout_ms);
|
||||
|
||||
/**
|
||||
* Load a file, allocate some extra bytes as well.
|
||||
@@ -104,3 +120,11 @@ static inline void* LoadFile(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* siz
|
||||
return LoadFileWithPadding(dir, path, size_ptr, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a temporary pointer to GUID.
|
||||
*/
|
||||
static inline EFI_GUID* TmpGuidPtr(EFI_GUID guid) {
|
||||
static EFI_GUID g;
|
||||
g = guid;
|
||||
return &g;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user