mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-07 09:36:10 -08:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
cbcb630697 | ||
|
|
8531c728e8 | ||
|
|
f255b13027 | ||
|
|
054f8cc751 | ||
|
|
24c4a8aa0c | ||
|
|
b3cc80b37b | ||
|
|
c01cf121a0 | ||
|
|
c12bd7a859 | ||
|
|
ce44c3dcb3 | ||
|
|
3b0253f6fc | ||
|
|
8921bafa90 | ||
|
|
0347a1d921 | ||
|
|
b78e5cd977 | ||
|
|
e1d51be11b | ||
|
|
19203eceed | ||
|
|
3da9e1818a | ||
|
|
b7fd08c978 | ||
|
|
ff838ec0f6 | ||
|
|
a627895bfb | ||
|
|
733acccc42 | ||
|
|
efdd91a6d8 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
boot*.efi
|
||||
setup.exe
|
||||
src/GIT_DESCRIBE.cs
|
||||
html
|
||||
95
CHANGELOG.md
Normal file
95
CHANGELOG.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 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
|
||||
- Use UTF-8 in the configuration file.
|
||||
- Use the default boot loader path if the configured one doesn't work.
|
||||
|
||||
## 1.3.0 - 2016-12-22
|
||||
|
||||
### Added
|
||||
- Check Secure Boot status before installing.
|
||||
|
||||
## 1.2.0 - 2016-06-05
|
||||
|
||||
### Added
|
||||
- Better installer, setup.exe.
|
||||
- Support for low-end machines with 32-bit IA-32 UEFI.
|
||||
- Support for changing resolution.
|
||||
- Version information in the program.
|
||||
- Change log.
|
||||
|
||||
### Removed
|
||||
- Removed old install scripts, install.bat and uninstall.bat.
|
||||
|
||||
## 1.1.0 - 2016-05-14
|
||||
|
||||
### Changed
|
||||
- Wait for input before booting if debug=1 is set.
|
||||
|
||||
### Fixed
|
||||
- Fix handling of multiple BGRT entries.
|
||||
- Fix ACPI table checksums.
|
||||
|
||||
## 1.0.0 - 2016-05-11
|
||||
|
||||
### Added
|
||||
- Easy-to-use installation script.
|
||||
- Git repository for the project.
|
||||
|
||||
## 0.2.0 - 2016-04-26
|
||||
|
||||
### Added
|
||||
- Support for randomly alternating images.
|
||||
- Support for black background.
|
||||
- Support for the native Windows logo.
|
||||
|
||||
### Changed
|
||||
- New configuration file format for images.
|
||||
|
||||
## 0.1.0 - 2016-01-15
|
||||
|
||||
### Added
|
||||
- Support for loading a bitmap and updating the BGRT.
|
||||
- Support for loading the next boot loader.
|
||||
- Support for a configuration file.
|
||||
3
Doxyfile
3
Doxyfile
@@ -1,8 +1,9 @@
|
||||
INPUT = src
|
||||
FILE_PATTERNS = *.c *.h
|
||||
FILE_PATTERNS = *.c *.h *.cs
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
EXTRACT_ALL = YES
|
||||
EXTRACT_STATIC = YES
|
||||
EXTRACT_PRIVATE = YES
|
||||
STRIP_CODE_COMMENTS = NO
|
||||
INLINE_SOURCES = NO
|
||||
GENERATE_HTML = YES
|
||||
|
||||
27
Makefile
27
Makefile
@@ -9,9 +9,29 @@ GNUEFI_LIB = /usr/$(CC_PREFIX)/lib
|
||||
|
||||
FILES_C = src/main.c src/util.c src/types.c src/config.c
|
||||
FILES_H = $(wildcard src/*.h)
|
||||
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs
|
||||
GIT_DESCRIBE = $(firstword $(shell git describe --tags) unknown)
|
||||
CFLAGS += '-DGIT_DESCRIBE=L"$(GIT_DESCRIBE)"'
|
||||
ZIPDIR = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||
ZIP = $(ZIPDIR).zip
|
||||
|
||||
.PHONY: all
|
||||
all: bootx64.efi
|
||||
all: efi setup zip
|
||||
efi: bootx64.efi bootia32.efi
|
||||
setup: setup.exe
|
||||
|
||||
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)"
|
||||
|
||||
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 /define:GIT_DESCRIBE /out:$@ $^
|
||||
|
||||
bootx64.efi: CC_PREFIX = x86_64-w64-mingw32
|
||||
bootx64.efi: GNUEFI_ARCH = x86_64
|
||||
@@ -22,6 +42,3 @@ bootia32.efi: CC_PREFIX = i686-w64-mingw32
|
||||
bootia32.efi: GNUEFI_ARCH = ia32
|
||||
bootia32.efi: $(FILES_C)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) -s
|
||||
|
||||
HackBGRT.tar.xz: bootx64.efi config.txt splash.bmp install.bat uninstall.bat README.md README.efilib LICENSE
|
||||
tar cJf $@ --transform=s,^,HackBGRT/, $^
|
||||
|
||||
75
README.md
75
README.md
@@ -4,42 +4,75 @@ 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.
|
||||
|
||||
## 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 you have a 64-bit x86-64 processor.
|
||||
* Make sure that Secure Boot is disabled, or learn to sign EFI applications.
|
||||
* Simple Windows installation:
|
||||
* Get at least these files: `bootx64.efi`, `config.txt`, `install.bat`, `splash.bmp`.
|
||||
* Run Command Prompt as Administrator.
|
||||
* Run `install.bat` from the Command Prompt.
|
||||
* The installer will launch Paint for creating the image(s).
|
||||
* The installer will launch Notepad for modifying the configuration.
|
||||
* If Windows later reinstalls the original boot loader, run `install.bat` again.
|
||||
* Installation for Windows with another boot loader (e.g. GRUB):
|
||||
* Copy the mentioned files 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:
|
||||
* Copy the mentioned files 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 Secure Boot is disabled, unless you know how to sign EFI applications.
|
||||
* Make sure that BitLocker is disabled, or find your recovery key.
|
||||
|
||||
### Windows installation
|
||||
|
||||
* Get the latest release from the Releases page.
|
||||
* Start `setup.exe` and follow the instructions.
|
||||
* You may need to manually disable Secure Boot and then retry.
|
||||
* 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!
|
||||
|
||||
### 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.
|
||||
* `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.
|
||||
* 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`.
|
||||
|
||||
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.
|
||||
The installer copies and converts files whose `path` starts with `\EFI\HackBGRT\`. For example, to use a file named `my.jpg`, copy it in the installer folder (same folder as `setup.exe`) and set the image path in `config.txt` to `path=\EFI\HackBGFT\my.jpg`.
|
||||
|
||||
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.
|
||||
|
||||
Advanced users may edit the `config.txt` to define multiple images, in which case one is picked at random.
|
||||
|
||||
## Recovery
|
||||
|
||||
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
|
||||
|
||||
|
||||
BIN
config.txt
Executable file → Normal file
BIN
config.txt
Executable file → Normal file
Binary file not shown.
120
install.bat
120
install.bat
@@ -1,120 +0,0 @@
|
||||
@ECHO OFF
|
||||
CD %~dp0
|
||||
|
||||
IF NOT "%1" == "uninstall" (
|
||||
IF NOT EXIST bootx64.efi (
|
||||
ECHO Missing bootx64.efi, you're doing something wrong.
|
||||
GOTO fail_before_esp
|
||||
)
|
||||
)
|
||||
|
||||
SET ESP_UNMOUNT=1
|
||||
SET ESP=-
|
||||
FOR /F "delims=" %%I IN ('CMD /C "MOUNTVOL | FINDSTR /C:EFI | FINDSTR /C::"') DO (
|
||||
ECHO %%I
|
||||
SET ESP_STR=%%I
|
||||
SET ESP=%ESP_STR:~-3,2%
|
||||
SET ESP_UNMOUNT=0
|
||||
)
|
||||
IF %ESP% == - MOUNTVOL S: /S >NUL && SET ESP=S:
|
||||
IF %ESP% == - MOUNTVOL B: /S >NUL && SET ESP=B:
|
||||
IF %ESP% == - MOUNTVOL A: /S >NUL && SET ESP=A:
|
||||
IF %ESP% == - MOUNTVOL X: /S >NUL && SET ESP=X:
|
||||
|
||||
IF %ESP% == - (
|
||||
ECHO The EFI System Partition is not mounted.
|
||||
GOTO fail_before_esp
|
||||
)
|
||||
|
||||
SET HackBGRT=%ESP%\EFI\HackBGRT
|
||||
SET MSBOOT=%ESP%\EFI\Microsoft\Boot
|
||||
|
||||
IF NOT EXIST %MSBOOT% (
|
||||
ECHO %MSBOOT% does not exist.
|
||||
ECHO If the path seems incorrect, report a bug.
|
||||
GOTO fail
|
||||
)
|
||||
|
||||
IF "%1" == "uninstall" (
|
||||
IF NOT EXIST %HackBGRT%\bootmgfw-original.efi (
|
||||
ECHO Missing %HackBGRT%\bootmgfw-original.efi!
|
||||
GOTO fail
|
||||
)
|
||||
COPY %HackBGRT%\bootmgfw-original.efi %MSBOOT%\bootmgfw.efi >NUL || (
|
||||
ECHO Failed to restore the original bootmgfw.efi.
|
||||
GOTO fail
|
||||
)
|
||||
ECHO The original bootmgfw.efi has been restored.
|
||||
IF EXIST %HackBGRT% (
|
||||
DEL /P %HackBGRT%
|
||||
)
|
||||
EXIT /B
|
||||
)
|
||||
|
||||
IF NOT EXIST %HackBGRT% (
|
||||
MKDIR %HackBGRT%
|
||||
)
|
||||
IF NOT EXIST %HackBGRT%\bootmgfw-original.efi (
|
||||
COPY %MSBOOT%\bootmgfw.efi %HackBGRT%\bootmgfw-original.efi >NUL || (
|
||||
ECHO Couldn't copy the original bootmgfw.efi.
|
||||
GOTO fail
|
||||
)
|
||||
)
|
||||
|
||||
ECHO Copying files...
|
||||
COPY /Y LICENSE %HackBGRT%\ >NUL
|
||||
COPY /Y README.md %HackBGRT%\ >NUL
|
||||
COPY /Y README.efilib %HackBGRT%\ >NUL
|
||||
COPY /Y install.bat %HackBGRT%\ >NUL
|
||||
COPY /Y uninstall.bat %HackBGRT%\ >NUL
|
||||
COPY /Y bootx64.efi %HackBGRT%\ >NUL || GOTO fail
|
||||
IF NOT EXIST %HackBGRT%\splash.bmp (
|
||||
COPY splash.bmp %HackBGRT%\ >NUL || GOTO fail
|
||||
)
|
||||
IF EXIST %HackBGRT%\config.txt (
|
||||
ECHO Copying configuration as config-new.txt.
|
||||
ECHO Be sure to check for any format changes!
|
||||
COPY /Y config.txt %HackBGRT%\config-new.txt >NUL || GOTO fail
|
||||
) ELSE (
|
||||
COPY /Y config.txt %HackBGRT%\config.txt >NUL || GOTO fail
|
||||
)
|
||||
|
||||
ECHO Draw or copy your preferred image to splash.bmp.
|
||||
START /WAIT mspaint %HackBGRT%\splash.bmp
|
||||
|
||||
ECHO Check the configuration in config.txt.
|
||||
IF EXIST %HackBGRT%\config-new.txt (
|
||||
ECHO See config-new.txt for reference.
|
||||
START notepad %HackBGRT%\config-new.txt
|
||||
)
|
||||
START /WAIT notepad %HackBGRT%\config.txt
|
||||
|
||||
ECHO Replacing bootmgfw.efi.
|
||||
COPY /Y bootx64.efi %MSBOOT%\bootmgfw.efi >NUL || (
|
||||
ECHO Failed to copy the boot loader!
|
||||
ECHO Restoring the original bootmgfw.efi...
|
||||
COPY %HackBGRT%\bootmgfw-original.efi %MSBOOT%\bootmgfw.efi >NUL || (
|
||||
ECHO Restoration failed You will need to fix this!
|
||||
)
|
||||
GOTO fail
|
||||
)
|
||||
|
||||
IF %ESP_UNMOUNT% == 1 (
|
||||
MOUNTVOL %ESP% /D
|
||||
)
|
||||
|
||||
ECHO Installation is ready.
|
||||
ECHO If your CPU is not x86-64, you should definitely uninstall now.
|
||||
ECHO Remember to disable Secure Boot, or HackBGRT will not boot.
|
||||
PAUSE
|
||||
EXIT /B
|
||||
|
||||
:fail
|
||||
IF %ESP_UNMOUNT% == 1 (
|
||||
MOUNTVOL %ESP% /D
|
||||
)
|
||||
|
||||
:fail_before_esp
|
||||
ECHO Exiting due to errors.
|
||||
PAUSE
|
||||
EXIT /B 1
|
||||
469
src/Efi.cs
Normal file
469
src/Efi.cs
Normal file
@@ -0,0 +1,469 @@
|
||||
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() {
|
||||
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>();
|
||||
while (pos + 4 <= pathNodesEnd) {
|
||||
var len = BitConverter.ToUInt16(data, pos + 2);
|
||||
if (len < 4 || pos + len > pathNodesEnd) {
|
||||
return; // throw new Exception("Bad entry.");
|
||||
}
|
||||
DevicePathNodes.Add(new DevicePathNode(data.Skip(pos).Take(len).ToArray()));
|
||||
pos += len;
|
||||
}
|
||||
Arguments = data.Skip(pathNodesEnd).ToArray();
|
||||
}
|
||||
public byte[] ToBytes() {
|
||||
return new byte[0]
|
||||
.Concat(BitConverter.GetBytes((UInt32) Attributes))
|
||||
.Concat(BitConverter.GetBytes((UInt16) DevicePathNodes.Sum(n => n.Data.Length + 4)))
|
||||
.Concat(Encoding.Unicode.GetBytes(Label + "\0"))
|
||||
.Concat(DevicePathNodes.SelectMany(n => n.ToBytes()))
|
||||
.Concat(Arguments)
|
||||
.ToArray();
|
||||
}
|
||||
public DevicePathNode FileNameNode {
|
||||
get {
|
||||
var d = DevicePathNodes;
|
||||
return d.Count > 1 && d[d.Count - 1].Type == 0x7F && d[d.Count - 2].Type == 0x04 ? d[d.Count - 2] : null;
|
||||
}
|
||||
}
|
||||
public bool HasFileName {
|
||||
get {
|
||||
return FileNameNode != null;
|
||||
}
|
||||
}
|
||||
public string FileName {
|
||||
get {
|
||||
if (!HasFileName) {
|
||||
return "";
|
||||
}
|
||||
return new string(Encoding.Unicode.GetChars(FileNameNode.Data).TakeWhile(c => c != '\0').ToArray());
|
||||
}
|
||||
set {
|
||||
if (!HasFileName) {
|
||||
throw new Exception("Logic error: Setting FileName on a bad boot entry.");
|
||||
}
|
||||
FileNameNode.Data = Encoding.Unicode.GetBytes(value + "\0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public const string EFI_GLOBAL_GUID = "{8be4df61-93ca-11d2-aa0d-00e098032b8c}";
|
||||
|
||||
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 dryRun Don't actually create the entry.
|
||||
*/
|
||||
public static void MakeAndEnableBootEntry(string label, string fileName, bool dryRun = false) {
|
||||
Variable msEntry = null, ownEntry = null;
|
||||
UInt16 msNum = 0, ownNum = 0;
|
||||
|
||||
// Find a free entry and the MS bootloader entry.
|
||||
Variable bootOrder = null;
|
||||
try {
|
||||
bootOrder = GetVariable("BootOrder");
|
||||
} catch {
|
||||
if (dryRun) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bootOrder == null || bootOrder.Data == null) {
|
||||
throw new Exception("MakeBootEntry: Could not read BootOrder. Maybe your computer is defective.");
|
||||
}
|
||||
var bootCurrent = GetVariable("BootCurrent");
|
||||
if (bootCurrent.Data == null) {
|
||||
throw new Exception("MakeBootEntry: Could not read BootCurrent. Maybe your computer is defective.");
|
||||
}
|
||||
Setup.Log($"Read EFI variable: {bootOrder}");
|
||||
Setup.Log($"Read EFI variable: {bootCurrent}");
|
||||
var bootOrderInts = new List<UInt16>(BytesToUInt16s(bootOrder.Data));
|
||||
foreach (var num in BytesToUInt16s(bootCurrent.Data).Concat(bootOrderInts).Concat(Enumerable.Range(0, 0xffff).Select(i => (UInt16) i))) {
|
||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
||||
if (entry.Data == null) {
|
||||
if (ownEntry == null) {
|
||||
ownNum = num;
|
||||
ownEntry = entry;
|
||||
}
|
||||
} else {
|
||||
var entryData = new BootEntryData(entry.Data);
|
||||
if (!entryData.HasFileName) {
|
||||
continue;
|
||||
}
|
||||
if (entryData.Label == label && entryData.FileName == fileName) {
|
||||
ownNum = num;
|
||||
ownEntry = entry;
|
||||
}
|
||||
if (msEntry == null && entryData.FileName.StartsWith("\\EFI\\Microsoft\\Boot\\bootmgfw.efi", StringComparison.OrdinalIgnoreCase)) {
|
||||
msNum = num;
|
||||
msEntry = entry;
|
||||
}
|
||||
}
|
||||
if (ownEntry != null && msEntry != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ownEntry == null) {
|
||||
throw new Exception("MakeBootEntry: Boot entry list is full.");
|
||||
} else if (msEntry == null) {
|
||||
throw new Exception("MakeBootEntry: Windows Boot Manager not found.");
|
||||
} else {
|
||||
Setup.Log($"Read EFI variable: {msEntry}");
|
||||
// Make a new boot entry using the MS entry as a starting point.
|
||||
var entryData = new BootEntryData(msEntry.Data);
|
||||
entryData.Arguments = Encoding.UTF8.GetBytes(label + "\0");
|
||||
entryData.Attributes = 1; // LOAD_OPTION_ACTIVE
|
||||
entryData.Label = label;
|
||||
entryData.FileName = fileName;
|
||||
ownEntry.Attributes = 7; // EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
|
||||
ownEntry.Data = entryData.ToBytes();
|
||||
SetVariable(ownEntry, dryRun);
|
||||
}
|
||||
|
||||
var msPos = bootOrderInts.IndexOf(msNum);
|
||||
var ownPos = bootOrderInts.IndexOf(ownNum);
|
||||
var mustAdd = ownPos == -1;
|
||||
var mustMove = 0 <= msPos && msPos <= ownPos;
|
||||
if (mustAdd || mustMove) {
|
||||
if (mustMove) {
|
||||
bootOrderInts.RemoveAt(ownPos);
|
||||
}
|
||||
bootOrderInts.Insert(msPos < 0 ? 0 : msPos, ownNum);
|
||||
bootOrder.Data = bootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||
SetVariable(bootOrder, dryRun);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the 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: {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: {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;
|
||||
}
|
||||
}
|
||||
}
|
||||
883
src/Setup.cs
Normal file
883
src/Setup.cs
Normal file
@@ -0,0 +1,883 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Principal;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: AssemblyInformationalVersionAttribute(GIT_DESCRIBE.data)]
|
||||
[assembly: AssemblyProductAttribute("HackBGRT")]
|
||||
|
||||
/**
|
||||
* HackBGRT Setup.
|
||||
*/
|
||||
public class Setup {
|
||||
/** @var Version of the setup program. */
|
||||
const string Version =
|
||||
#if GIT_DESCRIBE
|
||||
GIT_DESCRIBE.data
|
||||
#else
|
||||
"unknown; not an official release?"
|
||||
#endif
|
||||
;
|
||||
|
||||
/**
|
||||
* The custom exception class for expected exceptions.
|
||||
*/
|
||||
public class SetupException: Exception {
|
||||
public SetupException(string msg): base(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom exception class for simply exiting the application.
|
||||
*/
|
||||
public class ExitSetup: Exception {
|
||||
public readonly int Code;
|
||||
public ExitSetup(int code) {
|
||||
Code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot loader type.
|
||||
*/
|
||||
public enum BootLoaderType {
|
||||
None,
|
||||
Own,
|
||||
Microsoft,
|
||||
Other,
|
||||
}
|
||||
|
||||
/** @var The privileged actions. */
|
||||
protected static readonly string[] privilegedActions = new string[] {
|
||||
"install",
|
||||
"allow-secure-boot",
|
||||
"allow-bitlocker",
|
||||
"allow-bad-loader",
|
||||
"enable-entry", "disable-entry",
|
||||
"enable-bcdedit", "disable-bcdedit",
|
||||
"enable-overwrite", "disable-overwrite",
|
||||
"disable",
|
||||
"uninstall",
|
||||
"boot-to-fw",
|
||||
};
|
||||
|
||||
/** @var The target directory. */
|
||||
protected string InstallPath;
|
||||
|
||||
/** @var The backup MS boot loader path. */
|
||||
protected string BackupLoaderPath {
|
||||
get {
|
||||
return Path.Combine(InstallPath, "bootmgfw-original.efi");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var The EFI architecture identifier. */
|
||||
protected string EfiArch;
|
||||
|
||||
/** @var User-defined EFI architecture? */
|
||||
protected bool UserDefinedArch = false;
|
||||
|
||||
/** @var Arguments to forward to the elevated process. */
|
||||
protected string ForwardArguments;
|
||||
|
||||
/** @var Dry run? */
|
||||
protected bool DryRun;
|
||||
|
||||
/** @var Run in batch mode? */
|
||||
protected bool Batch;
|
||||
|
||||
/**
|
||||
* Output a line.
|
||||
*/
|
||||
public static void WriteLine(string s = "") {
|
||||
Console.WriteLine(s);
|
||||
Log(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a line to the log file.
|
||||
*/
|
||||
public static void Log(string s) {
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
var pid = System.Diagnostics.Process.GetCurrentProcess().Id;
|
||||
var prefix = $"{timestamp} | pid {pid} | ";
|
||||
File.AppendAllText("setup.log", prefix + s.Replace("\n", "\n" + prefix) + "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start another process.
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
* @return The started process.
|
||||
*/
|
||||
public static Process StartProcess(string app, string args) {
|
||||
Log($"StartProcess: {app} {args}");
|
||||
try {
|
||||
var info = new ProcessStartInfo(app, args);
|
||||
info.UseShellExecute = false;
|
||||
return Process.Start(info);
|
||||
} catch (Exception e) {
|
||||
Log($"StartProcess failed: {e.ToString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute another process, return the output.
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
* @param nullOnFail If set, return null if the program exits with a code other than 0, even if there was some output.
|
||||
* @return The output, or null if the execution failed.
|
||||
*/
|
||||
public static string Execute(string app, string args, bool nullOnFail) {
|
||||
Log($"Execute: {app} {args}");
|
||||
try {
|
||||
var info = new ProcessStartInfo(app, args);
|
||||
info.UseShellExecute = false;
|
||||
info.RedirectStandardOutput = true;
|
||||
var p = Process.Start(info);
|
||||
string output = p.StandardOutput.ReadToEnd();
|
||||
p.WaitForExit();
|
||||
Log($"Exit code: {p.ExitCode}, output:\n{output}\n\n");
|
||||
return (nullOnFail && p.ExitCode != 0) ? null : output;
|
||||
} catch (Exception e) {
|
||||
Log($"Execute failed: {e.ToString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for required privileges (access to ESP and EFI vars).
|
||||
*
|
||||
* @return True, if the file was successfully copied.
|
||||
*/
|
||||
public static bool HasPrivileges() {
|
||||
try {
|
||||
var id = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(id);
|
||||
var admin = WindowsBuiltInRole.Administrator;
|
||||
// FIXME: Check access to ESP as well.
|
||||
if (!principal.IsInRole(admin)) {
|
||||
return false;
|
||||
}
|
||||
Efi.EnablePrivilege();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log($"HasPrivileges failed: {e.ToString()}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run another process as admin, return the exit code.
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
*/
|
||||
public static int RunElevated(string app, string args) {
|
||||
Log($"RunElevated: {app} {args}");
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo(app);
|
||||
startInfo.Arguments = args;
|
||||
startInfo.Verb = "runas";
|
||||
startInfo.UseShellExecute = true;
|
||||
Process p = Process.Start(startInfo);
|
||||
p.WaitForExit();
|
||||
return p.ExitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this HackBGRT's own boot loader?
|
||||
*/
|
||||
public static BootLoaderType DetectLoader(string path) {
|
||||
try {
|
||||
var data = File.ReadAllBytes(path);
|
||||
string tmp = System.Text.Encoding.ASCII.GetString(data);
|
||||
if (tmp.IndexOf("HackBGRT") >= 0 || tmp.IndexOf("HackBgrt") >= 0) {
|
||||
return BootLoaderType.Own;
|
||||
} else if (tmp.IndexOf("Microsoft Corporation") >= 0) {
|
||||
return BootLoaderType.Microsoft;
|
||||
} else {
|
||||
return BootLoaderType.Other;
|
||||
}
|
||||
} catch (Exception e) when (e is FileNotFoundException || e is DirectoryNotFoundException) {
|
||||
Log($"DetectLoader failed: {path} not found");
|
||||
} catch (Exception e) {
|
||||
Log($"DetectLoader failed: {e.ToString()}");
|
||||
}
|
||||
return BootLoaderType.None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the EFI architecture from a file.
|
||||
*/
|
||||
public static string DetectArchFromFile(string path) {
|
||||
try {
|
||||
var data = File.ReadAllBytes(path);
|
||||
var peArch = BitConverter.ToUInt16(data, BitConverter.ToInt32(data, 0x3c) + 4);
|
||||
return peArch switch {
|
||||
0x014c => "ia32",
|
||||
0x0200 => "ia64",
|
||||
0x8664 => "x64",
|
||||
0xaa64 => "aa64",
|
||||
_ => $"unknown-{peArch:x4}"
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or mount or manually choose the EFI System Partition.
|
||||
*/
|
||||
protected void InitEspPath() {
|
||||
if (DryRun) {
|
||||
Directory.CreateDirectory(Path.Combine("dry-run", "EFI"));
|
||||
Esp.TryPath("dry-run", false);
|
||||
}
|
||||
if (Esp.Location == null && !Esp.Find() && !Esp.Mount() && !Batch) {
|
||||
WriteLine("EFI System Partition was not found.");
|
||||
WriteLine("Press enter to exit, or give ESP path here: ");
|
||||
string s = Console.ReadLine();
|
||||
Log($"User input: {s}");
|
||||
if (s.Length == 1) {
|
||||
s = s + ":";
|
||||
}
|
||||
if (!Esp.TryPath(s, true)) {
|
||||
WriteLine("That's not a valid ESP path!");
|
||||
}
|
||||
}
|
||||
if (Esp.Location == null) {
|
||||
throw new SetupException("EFI System Partition was not found.");
|
||||
}
|
||||
WriteLine($"EFI System Partition location is {Esp.Location}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a single file.
|
||||
*/
|
||||
protected void InstallFile(string name, string newName = null, bool prependInstallPath = true) {
|
||||
if (prependInstallPath) {
|
||||
newName = Path.Combine(InstallPath, newName == null ? name : newName);
|
||||
}
|
||||
try {
|
||||
File.Copy(name, newName, true);
|
||||
} catch (Exception e) {
|
||||
Log($"InstallFile failed: {e.ToString()}");
|
||||
throw new SetupException($"Failed to install file {name} to {newName}.");
|
||||
}
|
||||
WriteLine($"Installed {name} to {newName}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a single image file, with conversion to 24-bit BMP.
|
||||
*/
|
||||
protected void InstallImageFile(string name) {
|
||||
Log($"InstallImageFile: {name}");
|
||||
var newName = Path.Combine(InstallPath, name);
|
||||
// Load the image to check if it's valid.
|
||||
Bitmap img;
|
||||
try {
|
||||
img = new Bitmap(name);
|
||||
} catch {
|
||||
throw new SetupException($"Failed to load image {name}.");
|
||||
}
|
||||
// Copy the bitmap into an empty 24-bit image (required by EFI).
|
||||
using (Bitmap bmp = new Bitmap(img.Width, img.Height, PixelFormat.Format24bppRgb)) {
|
||||
using (Graphics g = Graphics.FromImage(bmp)) {
|
||||
g.DrawImageUnscaledAndClipped(img, new Rectangle(Point.Empty, img.Size));
|
||||
}
|
||||
try {
|
||||
bmp.Save(newName, ImageFormat.Bmp);
|
||||
} catch {
|
||||
throw new SetupException($"Failed to install image {name} to {newName}.");
|
||||
}
|
||||
}
|
||||
WriteLine($"Installed image {name} to {newName}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install files to ESP.
|
||||
*/
|
||||
protected void InstallFiles() {
|
||||
if (!File.Exists($"boot{EfiArch}.efi")) {
|
||||
throw new SetupException($"Missing boot{EfiArch}.efi, {EfiArch} is not supported!");
|
||||
}
|
||||
try {
|
||||
if (!Directory.Exists(InstallPath)) {
|
||||
Directory.CreateDirectory(InstallPath);
|
||||
}
|
||||
} catch {
|
||||
throw new SetupException("Failed to copy files to ESP!");
|
||||
}
|
||||
|
||||
InstallFile("config.txt");
|
||||
var lines = File.ReadAllLines("config.txt");
|
||||
Log($"config.txt:\n{String.Join("\n", lines)}");
|
||||
foreach (var line in lines.Where(s => s.StartsWith("image="))) {
|
||||
var delim = "path=\\EFI\\HackBGRT\\";
|
||||
var i = line.IndexOf(delim);
|
||||
if (i > 0) {
|
||||
InstallImageFile(line.Substring(i + delim.Length));
|
||||
}
|
||||
}
|
||||
InstallFile($"boot{EfiArch}.efi", "loader.efi");
|
||||
WriteLine($"HackBGRT has been copied to {InstallPath}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT with bcdedit.
|
||||
*/
|
||||
protected void EnableBCDEdit() {
|
||||
try {
|
||||
var re = new Regex("[{][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]");
|
||||
var guid = re.Match(Execute("bcdedit", "/copy {bootmgr} /d HackBGRT", true)).Value;
|
||||
Execute("bcdedit", $"/set {guid} device partition={Esp.Location}", true);
|
||||
Execute("bcdedit", $"/set {guid} path \\EFI\\HackBGRT\\loader.efi", true);
|
||||
foreach (var arg in new string[] { "locale", "inherit", "default", "resumeobject", "displayorder", "toolsdisplayorder", "timeout" }) {
|
||||
Execute("bcdedit", $"/deletevalue {guid} {arg}", true);
|
||||
}
|
||||
var fwbootmgr = "{fwbootmgr}";
|
||||
Execute("bcdedit", $"/set {fwbootmgr} displayorder {guid} /addfirst", true);
|
||||
} catch (Exception e) {
|
||||
Log($"EnableBCDEdit failed: {e.ToString()}");
|
||||
throw new SetupException("Failed to enable HackBGRT with BCDEdit!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HackBGRT with bcdedit.
|
||||
*/
|
||||
protected void DisableBCDEdit() {
|
||||
bool found = false, disabled = false;
|
||||
var fullOutput = Execute("bcdedit", "/enum firmware", true);
|
||||
if (fullOutput == null) {
|
||||
return;
|
||||
}
|
||||
var re = new Regex("[{][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[}]");
|
||||
var guids = (from Match match in re.Matches(fullOutput) select match.Value).Distinct().ToArray();
|
||||
foreach (var guid in guids) {
|
||||
var entry = Execute("bcdedit", $"/enum {guid}", true);
|
||||
if (entry == null) {
|
||||
Log($"DisableBCDEdit failed to enum {guid}.");
|
||||
} else if (entry.IndexOf("HackBGRT") >= 0) {
|
||||
found = true;
|
||||
Log($"Disabling HackBGRT entry {guid}.");
|
||||
if (Execute("bcdedit", $"/delete {guid}", true) == null) {
|
||||
Log($"DisableBCDEdit failed to delete {guid}.");
|
||||
}
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
if (found && !disabled) {
|
||||
throw new SetupException("Failed to disable HackBGRT with BCDEdit!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT boot entry.
|
||||
*/
|
||||
protected void EnableEntry() {
|
||||
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", DryRun);
|
||||
WriteLine("Enabled NVRAM entry for HackBGRT.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HackBGRT boot entry.
|
||||
*/
|
||||
protected void DisableEntry() {
|
||||
Efi.DisableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", DryRun);
|
||||
WriteLine("Disabled NVRAM entry for HackBGRT.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT by overwriting the MS boot loader.
|
||||
*/
|
||||
protected void OverwriteMsLoader() {
|
||||
var ms = Esp.MsLoaderPath;
|
||||
var backup = BackupLoaderPath;
|
||||
var own = Path.Combine(InstallPath, "loader.efi");
|
||||
|
||||
if (DetectLoader(ms) == BootLoaderType.Microsoft) {
|
||||
InstallFile(ms, backup, false);
|
||||
}
|
||||
if (DetectLoader(backup) != BootLoaderType.Microsoft) {
|
||||
// Duplicate check, but better to be sure...
|
||||
throw new SetupException("Missing MS boot loader backup!");
|
||||
}
|
||||
try {
|
||||
InstallFile(own, ms, false);
|
||||
} catch (SetupException e) {
|
||||
WriteLine(e.Message);
|
||||
if (DetectLoader(ms) != BootLoaderType.Microsoft) {
|
||||
try {
|
||||
InstallFile(backup, ms, false);
|
||||
} catch (SetupException e2) {
|
||||
WriteLine(e2.Message);
|
||||
throw new SetupException("Rollback failed, your system may be unbootable! Create a rescue disk IMMEADIATELY!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the MS boot loader if it was previously replaced.
|
||||
*/
|
||||
protected void RestoreMsLoader() {
|
||||
var ms = Esp.MsLoaderPath;
|
||||
if (DetectLoader(ms) == BootLoaderType.Own) {
|
||||
WriteLine("Disabling an old version of HackBGRT.");
|
||||
InstallFile(BackupLoaderPath, ms, false);
|
||||
WriteLine($"{ms} has been restored.");
|
||||
}
|
||||
if (DetectLoader(BackupLoaderPath) == BootLoaderType.Own) {
|
||||
File.Delete(BackupLoaderPath);
|
||||
WriteLine($"{BackupLoaderPath} was messed up and has been removed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that config.txt has a reasonable boot loader line.
|
||||
*/
|
||||
protected void VerifyLoaderConfig() {
|
||||
var lines = File.ReadAllLines("config.txt");
|
||||
var loader = lines.Where(s => s.StartsWith("boot=")).Select(s => s.Substring(5)).Prepend("").Last();
|
||||
if (loader == null) {
|
||||
throw new SetupException("config.txt does not contain a boot=... line!");
|
||||
}
|
||||
WriteLine($"Verifying config: boot={loader}");
|
||||
if (loader == "MS") {
|
||||
var backup = BackupLoaderPath;
|
||||
var type = DetectLoader(backup);
|
||||
if (type == BootLoaderType.Own) {
|
||||
throw new SetupException($"boot=MS = {backup} = HackBGRT. Prepare your rescue disk!");
|
||||
}
|
||||
if (type == BootLoaderType.None) {
|
||||
if (DetectLoader(Esp.MsLoaderPath) != BootLoaderType.Microsoft) {
|
||||
throw new SetupException("config.txt contains boot=MS, but MS boot loader is not found!");
|
||||
}
|
||||
}
|
||||
} else if (!loader.StartsWith("\\")) {
|
||||
throw new SetupException($"Boot loader must be boot=MS or boot=\\valid\\path!");
|
||||
} else {
|
||||
switch (DetectLoader(Path.Combine(Esp.Location, loader.Substring(1).Replace("\\", Path.DirectorySeparatorChar.ToString())))) {
|
||||
case BootLoaderType.Own:
|
||||
throw new SetupException($"Boot loader points back to HackBGRT!");
|
||||
case BootLoaderType.None:
|
||||
throw new SetupException($"Boot loader does not exist!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure HackBGRT.
|
||||
*/
|
||||
protected void Configure() {
|
||||
WriteLine("This setup program lets you edit just one image.");
|
||||
WriteLine("Edit config.txt manually for advanced configuration.");
|
||||
|
||||
// Open splash.bmp in mspaint.
|
||||
WriteLine("Draw or copy your preferred image to splash.bmp.");
|
||||
try {
|
||||
StartProcess("mspaint", "splash.bmp").WaitForExit();
|
||||
} catch {
|
||||
WriteLine("Editing splash.bmp with mspaint failed!");
|
||||
WriteLine("Edit splash.bmp with your preferred editor.");
|
||||
WriteLine("Press any key to continue.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HackBGRT completely.
|
||||
*/
|
||||
protected void Disable() {
|
||||
RestoreMsLoader();
|
||||
DisableBCDEdit();
|
||||
DisableEntry();
|
||||
WriteLine("HackBGRT has been disabled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall HackBGRT completely.
|
||||
*/
|
||||
protected void Uninstall() {
|
||||
Disable();
|
||||
try {
|
||||
Directory.Delete(InstallPath, true);
|
||||
WriteLine($"HackBGRT has been removed from {InstallPath}.");
|
||||
} catch (Exception e) {
|
||||
Log($"Uninstall failed: {e.ToString()}");
|
||||
throw new SetupException($"The directory {InstallPath} couldn't be removed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Secure Boot status and inform the user.
|
||||
*
|
||||
* @param allowSecureBoot Allow Secure Boot to be enabled?
|
||||
*/
|
||||
protected void HandleSecureBoot(bool allowSecureBoot) {
|
||||
int secureBoot = Efi.GetSecureBootStatus();
|
||||
if (secureBoot == 0) {
|
||||
WriteLine("Secure Boot is disabled, good!");
|
||||
} else {
|
||||
if (secureBoot == 1) {
|
||||
WriteLine("Secure Boot is probably enabled.");
|
||||
} else {
|
||||
WriteLine("Secure Boot status could not be determined.");
|
||||
}
|
||||
WriteLine("It's very important to disable Secure Boot before installing.");
|
||||
WriteLine("Otherwise your machine may become unbootable.");
|
||||
if (Batch) {
|
||||
if (allowSecureBoot) {
|
||||
return;
|
||||
}
|
||||
throw new SetupException("Aborting because of Secure Boot.");
|
||||
}
|
||||
WriteLine("Choose action (press a key):");
|
||||
WriteLine(" S = Enter EFI Setup to disable Secure Boot manually; requires reboot!");
|
||||
WriteLine(" I = Install anyway; THIS MAY BE DANGEROUS!");
|
||||
WriteLine(" C = Cancel");
|
||||
var k = Console.ReadKey().Key;
|
||||
Log($"User input: {k}");
|
||||
WriteLine();
|
||||
if (k == ConsoleKey.I) {
|
||||
WriteLine("Continuing. THIS MAY BE DANGEROUS!");
|
||||
} else if (k == ConsoleKey.S) {
|
||||
BootToFW();
|
||||
} else {
|
||||
throw new SetupException("Aborting because of Secure Boot.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check BitLocker status and inform the user.
|
||||
*
|
||||
* @param allowBitLocker Allow BitLocker to be enabled?
|
||||
*/
|
||||
protected void HandleBitLocker(bool allowBitLocker) {
|
||||
var output = Execute("manage-bde", "-status", true);
|
||||
if (output == null) {
|
||||
WriteLine("BitLocker status could not be determined.");
|
||||
return;
|
||||
}
|
||||
var reOn = new Regex(@"Conversion Status:\s*(Encr|Fully Encr)|Protection Status:\s*Protection On");
|
||||
var reOff = new Regex(@"Conversion Status:\s*(Fully Decrypted)|Protection Status:\s*Protection Off");
|
||||
var isOn = reOn.Match(output).Success;
|
||||
var isOff = reOff.Match(output).Success;
|
||||
if (!isOn && isOff) {
|
||||
WriteLine("BitLocker is disabled, good!");
|
||||
return;
|
||||
}
|
||||
if (isOn) {
|
||||
WriteLine("BitLocker is enabled. Make sure you have your recovery key!");
|
||||
} else {
|
||||
WriteLine("BitLocker status is unclear. Run manage-bde -status to check.");
|
||||
}
|
||||
if (Batch) {
|
||||
if (allowBitLocker) {
|
||||
return;
|
||||
}
|
||||
throw new SetupException("Aborting because of BitLocker.");
|
||||
}
|
||||
WriteLine("Choose action (press a key):");
|
||||
WriteLine(" I = Install anyway; THIS MAY BE DANGEROUS!");
|
||||
WriteLine(" C = Cancel");
|
||||
var k = Console.ReadKey().Key;
|
||||
Log($"User input: {k}");
|
||||
WriteLine();
|
||||
if (k == ConsoleKey.I) {
|
||||
WriteLine("Continuing. THIS MAY BE DANGEROUS!");
|
||||
} else {
|
||||
WriteLine("If you absolutely want HackBGRT, you can disable BitLocker.");
|
||||
throw new SetupException("Aborting because of BitLocker.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot to the UEFI setup.
|
||||
*/
|
||||
protected void BootToFW() {
|
||||
if (!Efi.CanBootToFW()) {
|
||||
throw new SetupException("On this computer, you will need to find the UEFI setup manually.");
|
||||
}
|
||||
WriteLine("Notice: if this fails, you can still enter the UEFI setup manually.");
|
||||
try {
|
||||
Efi.SetBootToFW();
|
||||
WriteLine("Rebooting now...");
|
||||
StartProcess("shutdown", "-f -r -t 1");
|
||||
} catch (Exception e) {
|
||||
Log($"BootToFW failed: {e.ToString()}");
|
||||
throw new SetupException("Failed to reboot to UEFI setup! Do it manually.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the EFI architecture.
|
||||
*/
|
||||
protected static string DetectArchFromOS() {
|
||||
var arch = RuntimeInformation.OSArchitecture;
|
||||
return arch switch {
|
||||
Architecture.X86 => "ia32",
|
||||
Architecture.X64 => "x64",
|
||||
Architecture.Arm => "arm",
|
||||
Architecture.Arm64 => "aa64",
|
||||
_ => $"-unsupported-{arch}"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the EFI architecture.
|
||||
*
|
||||
* @param arch The architecture.
|
||||
*/
|
||||
protected void SetArch(string arch) {
|
||||
var detectedArch = DetectArchFromOS();
|
||||
if (arch == "") {
|
||||
EfiArch = detectedArch;
|
||||
WriteLine($"Your OS uses arch={EfiArch}. This will be checked again during installation.");
|
||||
} else {
|
||||
EfiArch = arch;
|
||||
UserDefinedArch = true;
|
||||
WriteLine($"Using the given arch={arch}");
|
||||
if (arch != detectedArch) {
|
||||
WriteLine($"Warning: arch={arch} is not the same as the detected arch={detectedArch}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize information for the Setup.
|
||||
*/
|
||||
protected void InitEspInfo() {
|
||||
InstallPath = Path.Combine(Esp.Location, "EFI", "HackBGRT");
|
||||
var detectedArch = DetectArchFromFile(Esp.MsLoaderPath);
|
||||
if (detectedArch == null) {
|
||||
WriteLine($"Failed to detect arch from MS boot loader, using arch={EfiArch}.");
|
||||
} else if (detectedArch == EfiArch || !UserDefinedArch) {
|
||||
WriteLine($"Detected arch={detectedArch} from MS boot loader, the installer will use that.");
|
||||
EfiArch = detectedArch;
|
||||
} else {
|
||||
WriteLine($"WARNING: You have set arch={EfiArch}, but detected arch={detectedArch} from MS boot loader.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for user's choice and install/uninstall.
|
||||
*/
|
||||
protected void ShowMenu() {
|
||||
WriteLine();
|
||||
WriteLine("Choose action (press a key):");
|
||||
WriteLine(" I = install");
|
||||
WriteLine(" - creates a new EFI boot entry for HackBGRT");
|
||||
WriteLine(" - sometimes needs to be enabled in firmware settings");
|
||||
WriteLine(" - should fall back to MS boot loader if HackBGRT fails");
|
||||
WriteLine(" J = install (alternative)");
|
||||
WriteLine(" - creates a new EFI boot entry with an alternative method (BCDEdit)");
|
||||
WriteLine(" - always sets HackBGRT as the first boot option");
|
||||
WriteLine(" - sometimes shows up as \"Windows Boot Manager\"");
|
||||
WriteLine(" - should fall back to MS boot loader if HackBGRT fails");
|
||||
WriteLine(" O = install (legacy)");
|
||||
WriteLine(" - overwrites the MS boot loader");
|
||||
WriteLine(" - may require re-install after Windows updates");
|
||||
WriteLine(" - could brick your system if configured incorrectly");
|
||||
WriteLine(" F = install files only");
|
||||
WriteLine(" - needs to be enabled somehow");
|
||||
WriteLine(" D = disable");
|
||||
WriteLine(" - removes created entries, restores MS boot loader");
|
||||
WriteLine(" R = remove completely");
|
||||
WriteLine(" - disables, then deletes all files and images");
|
||||
WriteLine(" B = boot to UEFI setup");
|
||||
WriteLine(" - lets you disable Secure Boot");
|
||||
WriteLine(" - lets you move HackBGRT before Windows in boot order");
|
||||
WriteLine(" C = cancel");
|
||||
|
||||
var k = Console.ReadKey().Key;
|
||||
Log($"User input: {k}");
|
||||
WriteLine();
|
||||
if (k == ConsoleKey.I || k == ConsoleKey.O || k == ConsoleKey.F) {
|
||||
Configure();
|
||||
}
|
||||
if (k == ConsoleKey.I) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-entry" });
|
||||
} else if (k == ConsoleKey.J) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-bcdedit" });
|
||||
} else if (k == ConsoleKey.O) {
|
||||
RunPrivilegedActions(new string[] { "install", "enable-overwrite" });
|
||||
} else if (k == ConsoleKey.F) {
|
||||
RunPrivilegedActions(new string[] { "install" });
|
||||
} else if (k == ConsoleKey.D) {
|
||||
RunPrivilegedActions(new string[] { "disable" });
|
||||
} else if (k == ConsoleKey.R) {
|
||||
RunPrivilegedActions(new string[] { "uninstall" });
|
||||
} else if (k == ConsoleKey.B) {
|
||||
RunPrivilegedActions(new string[] { "boot-to-fw" });
|
||||
} else if (k == ConsoleKey.C) {
|
||||
throw new ExitSetup(1);
|
||||
} else {
|
||||
throw new SetupException("Invalid choice!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run privileged actions.
|
||||
*
|
||||
* @param actions The actions to run.
|
||||
*/
|
||||
protected void RunPrivilegedActions(IEnumerable<string> actions) {
|
||||
var args = String.Join(" ", actions);
|
||||
Log($"RunPrivilegedActions: {args}");
|
||||
if (!HasPrivileges() && !DryRun) {
|
||||
var self = Assembly.GetExecutingAssembly().Location;
|
||||
var result = 0;
|
||||
try {
|
||||
result = RunElevated(self, $"is-elevated {ForwardArguments} {args}");
|
||||
} catch (Exception e) {
|
||||
Setup.Log(e.ToString());
|
||||
throw new SetupException($"Privileged action ({args}) failed: {e.Message}");
|
||||
}
|
||||
if (result != 0) {
|
||||
throw new SetupException($"Privileged action ({args}) failed!");
|
||||
}
|
||||
WriteLine($"Privileged action ({args}) completed.");
|
||||
return;
|
||||
}
|
||||
|
||||
InitEspPath();
|
||||
InitEspInfo();
|
||||
Efi.LogBGRT();
|
||||
bool allowSecureBoot = false;
|
||||
bool allowBitLocker = false;
|
||||
bool allowBadLoader = false;
|
||||
Action<Action> verify = (Action revert) => {
|
||||
try {
|
||||
VerifyLoaderConfig();
|
||||
} catch (SetupException e) {
|
||||
if (allowBadLoader) {
|
||||
WriteLine($"Warning: {e.Message}");
|
||||
} else {
|
||||
WriteLine($"Error: {e.Message}");
|
||||
WriteLine($"Reverting. Use batch mode with allow-bad-loader to override.");
|
||||
revert();
|
||||
throw new SetupException("Check your configuration and try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
Action<Action, Action> enable = (Action enable, Action revert) => {
|
||||
HandleSecureBoot(allowSecureBoot);
|
||||
HandleBitLocker(allowBitLocker);
|
||||
enable();
|
||||
verify(revert);
|
||||
};
|
||||
foreach (var arg in actions) {
|
||||
Log($"Running action '{arg}'.");
|
||||
if (arg == "install") {
|
||||
InstallFiles();
|
||||
} else if (arg == "allow-secure-boot") {
|
||||
allowSecureBoot = true;
|
||||
} else if (arg == "allow-bitlocker") {
|
||||
allowBitLocker = true;
|
||||
} else if (arg == "allow-bad-loader") {
|
||||
allowBadLoader = true;
|
||||
} else if (arg == "enable-entry") {
|
||||
enable(() => EnableEntry(), () => DisableEntry());
|
||||
} else if (arg == "disable-entry") {
|
||||
DisableEntry();
|
||||
} else if (arg == "enable-bcdedit") {
|
||||
enable(() => EnableBCDEdit(), () => DisableBCDEdit());
|
||||
} else if (arg == "disable-bcdedit") {
|
||||
DisableBCDEdit();
|
||||
} else if (arg == "enable-overwrite") {
|
||||
enable(() => OverwriteMsLoader(), () => RestoreMsLoader());
|
||||
} else if (arg == "disable-overwrite") {
|
||||
RestoreMsLoader();
|
||||
} else if (arg == "disable") {
|
||||
Disable();
|
||||
} else if (arg == "uninstall") {
|
||||
Uninstall();
|
||||
} else if (arg == "boot-to-fw") {
|
||||
BootToFW();
|
||||
} else {
|
||||
throw new SetupException($"Invalid action: '{arg}'!");
|
||||
}
|
||||
WriteLine($"Completed action '{arg}' successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Main program.
|
||||
*
|
||||
* @param args The arguments.
|
||||
*/
|
||||
public static void Main(string[] args) {
|
||||
var self = Assembly.GetExecutingAssembly().Location;
|
||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(self));
|
||||
WriteLine($"HackBGRT installer version: {Version}");
|
||||
Log($"Args: {String.Join(" ", args)}");
|
||||
Environment.ExitCode = new Setup().Run(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the setup.
|
||||
*
|
||||
* @param args The arguments.
|
||||
*/
|
||||
protected int Run(string[] args) {
|
||||
DryRun = args.Contains("dry-run");
|
||||
Batch = args.Contains("batch");
|
||||
ForwardArguments = String.Join(" ", args.Where(s => s == "dry-run" || s == "batch" || s.StartsWith("arch=")));
|
||||
try {
|
||||
SetArch(args.Prepend("arch=").Last(s => s.StartsWith("arch=")).Substring(5));
|
||||
if (args.Contains("is-elevated") && !HasPrivileges() && !DryRun) {
|
||||
WriteLine("This installer needs to be run as administrator!");
|
||||
return 1;
|
||||
}
|
||||
var actions = args.Where(s => privilegedActions.Contains(s));
|
||||
if (actions.Count() > 0) {
|
||||
RunPrivilegedActions(actions);
|
||||
return 0;
|
||||
}
|
||||
if (Batch) {
|
||||
throw new SetupException("No action specified!");
|
||||
}
|
||||
ShowMenu();
|
||||
WriteLine();
|
||||
WriteLine("All done!");
|
||||
return 0;
|
||||
} catch (ExitSetup e) {
|
||||
return e.Code;
|
||||
} catch (SetupException e) {
|
||||
WriteLine();
|
||||
WriteLine($"Error: {e.Message}");
|
||||
Log(e.ToString());
|
||||
return 1;
|
||||
} catch (Exception e) {
|
||||
WriteLine();
|
||||
WriteLine($"Unexpected error: {e.Message}");
|
||||
Log(e.ToString());
|
||||
WriteLine("If this is the most current release, please report this bug.");
|
||||
return 1;
|
||||
} finally {
|
||||
if (DryRun) {
|
||||
WriteLine("This was a dry run, your system was not actually modified.");
|
||||
}
|
||||
if (!Batch) {
|
||||
WriteLine("If you need to report a bug, please include the setup.log file.");
|
||||
WriteLine("Press any key to quit.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/config.c
87
src/config.c
@@ -4,14 +4,55 @@
|
||||
#include <efilib.h>
|
||||
|
||||
BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
CHAR16* str = 0;
|
||||
UINTN str_bytes = 0;
|
||||
str = LoadFileWithPadding(root_dir, path, &str_bytes, sizeof(*str));
|
||||
if (!str) {
|
||||
void* data = 0;
|
||||
UINTN data_bytes = 0;
|
||||
data = LoadFileWithPadding(root_dir, path, &data_bytes, 4);
|
||||
if (!data) {
|
||||
Print(L"HackBGRT: Failed to load configuration (%s)!\n", path);
|
||||
return FALSE;
|
||||
}
|
||||
UINTN str_len = str_bytes / sizeof(*str);
|
||||
CHAR16* str;
|
||||
UINTN str_len;
|
||||
if (*(CHAR16*)data == 0xfeff) {
|
||||
// UCS-2
|
||||
str = data;
|
||||
str_len = data_bytes / sizeof(*str);
|
||||
} else {
|
||||
// UTF-8 -> UCS-2
|
||||
EFI_STATUS e = BS->AllocatePool(EfiBootServicesData, data_bytes * 2 + 2, (void**)&str);
|
||||
if (EFI_ERROR(e)) {
|
||||
FreePool(data);
|
||||
return FALSE;
|
||||
}
|
||||
UINT8* str0 = data;
|
||||
for (UINTN i = str_len = 0; i < data_bytes;) {
|
||||
UINTN unicode = 0xfffd;
|
||||
if (str0[i] < 0x80) {
|
||||
unicode = str0[i];
|
||||
i += 1;
|
||||
} else if (str0[i] < 0xc0) {
|
||||
i += 1;
|
||||
} else if (str0[i] < 0xe0) {
|
||||
unicode = ((str0[i] & 0x1f) << 6) | ((str0[i+1] & 0x3f) << 0);
|
||||
i += 2;
|
||||
} else if (str0[i] < 0xf0) {
|
||||
unicode = ((str0[i] & 0x0f) << 12) | ((str0[i+1] & 0x3f) << 6) | ((str0[i+2] & 0x3f) << 0);
|
||||
i += 3;
|
||||
} else if (str0[i] < 0xf8) {
|
||||
unicode = ((str0[i] & 0x07) << 18) | ((str0[i+1] & 0x3f) << 12) | ((str0[i+2] & 0x3f) << 6) | ((str0[i+3] & 0x3f) << 0);
|
||||
i += 4;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
if (unicode <= 0xffff) {
|
||||
str[str_len++] = unicode;
|
||||
} else {
|
||||
str[str_len++] = 0xfffd;
|
||||
}
|
||||
}
|
||||
str[str_len] = 0;
|
||||
FreePool(data);
|
||||
}
|
||||
|
||||
for (int i = 0; i < str_len;) {
|
||||
int j = i;
|
||||
@@ -29,35 +70,37 @@ BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir,
|
||||
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);
|
||||
Print(L"HackBGRT: weight %d, action %d, x %d, y %d, o %d, path %s, random = %08x, limit = %08x\n", weight, action, x, y, o, path, random, limit);
|
||||
}
|
||||
if (!config->image_weight_sum || 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) {
|
||||
@@ -73,7 +116,21 @@ static void ReadConfigImage(struct HackBGRT_config* config, const CHAR16* 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) {
|
||||
const CHAR16* x = line;
|
||||
const CHAR16* y = StrStrAfter(line, L"x");
|
||||
if (x && *x && y && *y) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* line) {
|
||||
@@ -98,5 +155,9 @@ void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, co
|
||||
ReadConfigFile(config, root_dir, line + 7);
|
||||
return;
|
||||
}
|
||||
if (StrnCmp(line, L"resolution=", 11) == 0) {
|
||||
ReadConfigResolution(config, line + 11);
|
||||
return;
|
||||
}
|
||||
Print(L"Unknown configuration directive: %s\n", line);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ enum HackBGRT_action {
|
||||
* @see struct HackBGRT_config
|
||||
*/
|
||||
enum HackBGRT_coordinate {
|
||||
HackBGRT_coord_auto = 0x10000001,
|
||||
HackBGRT_coord_native = 0x10000002
|
||||
HackBGRT_coord_keep = -1000000001
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,6 +27,11 @@ struct HackBGRT_config {
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
316
src/main.c
316
src/main.c
@@ -8,7 +8,7 @@
|
||||
/**
|
||||
* The Print function signature.
|
||||
*/
|
||||
typedef UINTN print_t(IN CHAR16 *fmt, ...);
|
||||
typedef UINTN print_t(IN CONST CHAR16 *fmt, ...);
|
||||
|
||||
/**
|
||||
* The function for debug printing; either Print or NullPrint.
|
||||
@@ -35,21 +35,64 @@ static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the correct coordinate (manual, automatic, native)
|
||||
* Set screen resolution. If there is no exact match, try to find a bigger one.
|
||||
*
|
||||
* @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
|
||||
* @param w Horizontal resolution. 0 for max, -1 for current.
|
||||
* @param h Vertical resolution. 0 for max, -1 for current.
|
||||
*/
|
||||
static int SelectCoordinate(int value, int automatic, int native) {
|
||||
if (value == HackBGRT_coord_auto) {
|
||||
return automatic;
|
||||
static void SetResolution(int w, int h) {
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = GOP();
|
||||
if (!gop) {
|
||||
config.old_resolution_x = config.resolution_x = 0;
|
||||
config.old_resolution_y = config.resolution_y = 0;
|
||||
Debug(L"GOP not found!\n");
|
||||
return;
|
||||
}
|
||||
if (value == HackBGRT_coord_native) {
|
||||
return native;
|
||||
UINTN best_i = gop->Mode->Mode;
|
||||
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);
|
||||
for (UINT32 i = gop->Mode->MaxMode; i--;) {
|
||||
int new_w = 0, new_h = 0;
|
||||
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* info = 0;
|
||||
UINTN info_size;
|
||||
if (EFI_ERROR(gop->QueryMode(gop, i, &info_size, &info))) {
|
||||
continue;
|
||||
}
|
||||
if (info_size < sizeof(*info)) {
|
||||
FreePool(info);
|
||||
continue;
|
||||
}
|
||||
new_w = info->HorizontalResolution;
|
||||
new_h = info->VerticalResolution;
|
||||
FreePool(info);
|
||||
|
||||
// Sum of missing w/h should be minimal.
|
||||
int new_missing = max(w - new_w, 0) + max(h - new_h, 0);
|
||||
int best_missing = max(w - best_w, 0) + max(h - best_h, 0);
|
||||
if (new_missing > best_missing) {
|
||||
continue;
|
||||
}
|
||||
// Sum of extra w/h should be minimal.
|
||||
int new_over = max(-w + new_w, 0) + max(-h + new_h, 0);
|
||||
int best_over = max(-w + best_w, 0) + max(-h + best_h, 0);
|
||||
if (new_missing == best_missing && new_over >= best_over) {
|
||||
continue;
|
||||
}
|
||||
best_w = new_w;
|
||||
best_h = new_h;
|
||||
best_i = i;
|
||||
}
|
||||
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);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +139,7 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
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));
|
||||
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)) {
|
||||
@@ -106,7 +149,7 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
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);
|
||||
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++) {
|
||||
@@ -114,7 +157,7 @@ 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));
|
||||
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) {
|
||||
@@ -151,6 +194,44 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
return bgrt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a BMP with the given size and color.
|
||||
*
|
||||
* @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* MakeBMP(int w, int h, UINT8 r, UINT8 g, UINT8 b) {
|
||||
BMP* bmp = 0;
|
||||
BS->AllocatePool(EfiBootServicesData, 54 + w * h * 4, (void**) &bmp);
|
||||
if (!bmp) {
|
||||
Print(L"HackBGRT: 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.
|
||||
*
|
||||
@@ -159,32 +240,50 @@ static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt)
|
||||
* @return The loaded BMP, or 0 if not available.
|
||||
*/
|
||||
static BMP* LoadBMP(EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
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;
|
||||
return MakeBMP(1, 1, 0, 0, 0); // empty path = black image
|
||||
}
|
||||
Debug(L"HackBGRT: Loading %s.\n", path);
|
||||
bmp = LoadFile(root_dir, path, 0);
|
||||
if (!bmp) {
|
||||
Print(L"HackBGRT: Failed to load BMP (%s)!\n", path);
|
||||
BS->Stall(1000000);
|
||||
return 0;
|
||||
}
|
||||
UINTN size = 0;
|
||||
BMP* bmp = LoadFile(root_dir, path, &size);
|
||||
if (bmp) {
|
||||
if (size >= bmp->file_size && CompareMem(bmp, "BM", 2) == 0 && bmp->file_size - bmp->pixel_data_offset > 4 && bmp->width && bmp->height && (bmp->bpp == 32 || bmp->bpp == 24) && bmp->compression == 0) {
|
||||
return bmp;
|
||||
}
|
||||
Print(L"HackBGRT: Invalid BMP (%s)!\n", path);
|
||||
} else {
|
||||
Print(L"HackBGRT: 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 h_max = (bmp->file_size - bmp->pixel_data_offset) / old_pitch;
|
||||
bmp->height = min(bmp->height, h_max);
|
||||
const int new_pitch = -(-(bmp->width * (bmp->bpp / 8)) & ~3);
|
||||
|
||||
if (new_pitch < old_pitch) {
|
||||
for (int i = 1; i < bmp->height; ++i) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,14 +301,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) {
|
||||
@@ -225,12 +325,19 @@ void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -244,28 +351,52 @@ 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));
|
||||
|
||||
Debug(
|
||||
L"HackBGRT: BMP at (%d, %d), center (%d, %d), resolution (%d, %d) with orientation %d applied.\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);
|
||||
HandleAcpiTables(HackBGRT_REPLACE, bgrt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an application.
|
||||
*/
|
||||
static EFI_HANDLE LoadApp(print_t* 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;
|
||||
Debug(L"HackBGRT: Loading application %s.\n", path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &result))) {
|
||||
print_failure(L"HackBGRT: Failed to load application %s.\n", path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main program.
|
||||
*/
|
||||
@@ -295,36 +426,63 @@ EFI_STATUS EFIAPI EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
}
|
||||
Debug = config.debug ? Print : NullPrint;
|
||||
|
||||
SetResolution(config.resolution_x, config.resolution_y);
|
||||
HackBgrt(root_dir);
|
||||
|
||||
if (!config.boot_path) {
|
||||
Print(L"HackBGRT: Boot path not specified.\n");
|
||||
goto fail;
|
||||
}
|
||||
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";
|
||||
|
||||
Debug(L"HackBGRT: Loading %s.\n", config.boot_path);
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, (CHAR16*) config.boot_path);
|
||||
EFI_HANDLE next_image_handle;
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &next_image_handle))) {
|
||||
Print(L"HackBGRT: LoadImage for new image (%s) failed.\n", config.boot_path);
|
||||
if (config.boot_path && StriCmp(config.boot_path, L"MS") != 0) {
|
||||
next_image_handle = LoadApp(Print, image_handle, image, config.boot_path);
|
||||
} else {
|
||||
config.boot_path = backup_boot_path;
|
||||
next_image_handle = LoadApp(Debug, image_handle, image, config.boot_path);
|
||||
if (!next_image_handle) {
|
||||
config.boot_path = ms_boot_path;
|
||||
next_image_handle = LoadApp(Debug, image_handle, image, config.boot_path);
|
||||
}
|
||||
}
|
||||
if (!next_image_handle) {
|
||||
config.boot_path = backup_boot_path;
|
||||
next_image_handle = LoadApp(Print, image_handle, image, config.boot_path);
|
||||
if (!next_image_handle) {
|
||||
config.boot_path = ms_boot_path;
|
||||
next_image_handle = LoadApp(Print, image_handle, image, config.boot_path);
|
||||
if (!next_image_handle) {
|
||||
goto fail;
|
||||
}
|
||||
if (config.debug) {
|
||||
Print(L"HackBGRT: Ready to boot.\nPress escape to cancel, any other key to boot.\n");
|
||||
if (ReadKey().ScanCode == SCAN_ESC) {
|
||||
}
|
||||
Print(L"HackBGRT: Reverting to %s.\n", config.boot_path);
|
||||
Print(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 if (config.debug) {
|
||||
Print(L"HackBGRT: Ready to boot. Disable debug mode to skip this screen.\n");
|
||||
Print(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 (EFI_ERROR(BS->StartImage(next_image_handle, 0, 0))) {
|
||||
Print(L"HackBGRT: StartImage for %s failed.\n", config.boot_path);
|
||||
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);
|
||||
Print(L"Please check that %s is not actually HackBGRT!\n", config.boot_path);
|
||||
goto fail;
|
||||
|
||||
fail: {
|
||||
Print(L"HackBGRT has failed. Use parameter debug=1 for details.\nPress any key to exit.\n");
|
||||
ReadKey();
|
||||
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 (or wait 15 seconds) to exit.\n");
|
||||
ReadKey(15000);
|
||||
return 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;
|
||||
|
||||
/**
|
||||
|
||||
15
src/util.c
15
src/util.c
@@ -14,7 +14,7 @@ const CHAR16* TmpStr(CHAR8 *src, int length) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
UINTN NullPrint(IN CHAR16 *fmt, ...) {
|
||||
UINTN NullPrint(IN CONST CHAR16 *fmt, ...) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -68,14 +68,15 @@ 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;
|
||||
return WaitForSingleEvent(ST->ConIn->WaitForKey, timeout_ms * ms_to_100ns);
|
||||
}
|
||||
|
||||
EFI_INPUT_KEY ReadKey(void) {
|
||||
WaitKey();
|
||||
EFI_INPUT_KEY ReadKey(UINT64 timeout_ms) {
|
||||
EFI_INPUT_KEY key = {0};
|
||||
WaitKey(timeout_ms);
|
||||
ST->ConIn->ReadKeyStroke(ST->ConIn, &key);
|
||||
return key;
|
||||
}
|
||||
@@ -100,7 +101,9 @@ void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_p
|
||||
return 0;
|
||||
}
|
||||
e = handle->Read(handle, &size, data);
|
||||
*(UINT32*)((char*)data + size) = 0;
|
||||
for (int i = 0; i < padding; ++i) {
|
||||
*((char*)data + size + i) = 0;
|
||||
}
|
||||
handle->Close(handle);
|
||||
if (EFI_ERROR(e)) {
|
||||
FreePool(data);
|
||||
|
||||
@@ -14,7 +14,7 @@ extern const CHAR16* TmpStr(CHAR8 *src, int length);
|
||||
/**
|
||||
* Empty function that has the same signature as Print.
|
||||
*/
|
||||
extern UINTN NullPrint(IN CHAR16 *fmt, ...);
|
||||
extern UINTN NullPrint(IN CONST CHAR16 *fmt, ...);
|
||||
|
||||
/**
|
||||
* Return the greater of two numbers.
|
||||
@@ -82,15 +82,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.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@ECHO OFF
|
||||
CD %~dp0
|
||||
|
||||
IF NOT EXIST install.bat (
|
||||
ECHO The uninstaller needs install.bat!
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
CALL install.bat uninstall
|
||||
Reference in New Issue
Block a user