63 Commits

Author SHA1 Message Date
Lauri Kenttä
da9909bbdd Update change log and tag v2.1.0 2023-10-04 15:25:03 +03:00
Lauri Kenttä
0ce904f133 Use 999999 as the 'big' value instead of 2**31-1 2023-10-04 15:25:03 +03:00
Lauri Kenttä
62c892009a Overwrite BGRT in a more readable manner 2023-10-04 15:25:03 +03:00
Lauri Kenttä
00bbfd6d82 Add ACPI table addresses to debug output 2023-10-04 15:25:03 +03:00
Lauri Kenttä
1e566a05a0 Clarify README for image conversion 2023-10-04 15:25:03 +03:00
Lauri Kenttä
1058f3e2b4 Log BGRT contents during setup 2023-10-04 15:25:03 +03:00
Lauri Kenttä
661758ba58 Crop BMP to screen size 2023-09-14 18:07:48 +03:00
Lauri Kenttä
93eec1a250 Improve BMP handling 2023-09-14 18:05:38 +03:00
Lauri Kenttä
33e4450d19 Check that BitLocker is disabled 2023-09-10 23:03:50 +03:00
Lauri Kenttä
99bdf5a310 Update change log and tag v2.0.0 2023-09-10 15:43:04 +03:00
Lauri Kenttä
d2bac39680 Add assembly information to setup.exe 2023-09-10 15:43:04 +03:00
Lauri Kenttä
a4c917294c Add option to boot to firmware 2023-09-10 15:43:04 +03:00
Lauri Kenttä
d6da4b7cee Support creating boot entry with bcdedit 2023-09-10 15:42:18 +03:00
Lauri Kenttä
f2185f624d Detect issues with boot= option 2023-09-10 15:02:58 +03:00
Lauri Kenttä
75e64a7ef3 Handle screen orientation 2023-09-10 15:02:58 +03:00
Lauri Kenttä
e5616c6cd2 Make coordinates relative to center
Relative coordinates make it easier to center the image.
The value "auto" is now replaced with simply 0.
The value "native" is replaced with "keep" for consistency.
The value is clamped, so x=-9999 will align to the left border etc.
2023-09-10 15:02:58 +03:00
Lauri Kenttä
ad0b71c49b Set EFI ReadKey timeout to 15 seconds 2023-09-10 15:02:58 +03:00
Lauri Kenttä
e44ce9f5ee Add logging 2023-09-10 15:02:58 +03:00
Lauri Kenttä
5f3c6afc23 Remove BootLoaderInfo class 2023-09-10 15:02:58 +03:00
Lauri Kenttä
1fa53f3f06 Remove SetupHelper class 2023-09-10 15:02:58 +03:00
Lauri Kenttä
8301a16ca8 Support arch=... parameter, improve auto-detection 2023-09-10 15:02:58 +03:00
Lauri Kenttä
de9d0d984a Add partial Linux support to installer 2023-09-10 15:02:58 +03:00
Lauri Kenttä
7dbdf33ea8 Create an own entry, don't replace MS boot loader 2023-09-10 15:02:58 +03:00
Lauri Kenttä
dfb5b916ed Elevate privileges only when needed 2023-09-10 15:02:58 +03:00
Lauri Kenttä
267af0bd9c Support dry run in the installer 2023-09-10 15:02:58 +03:00
Lauri Kenttä
be5894b387 Check for HackBGRT string when disabling old version 2023-09-10 15:02:58 +03:00
Lauri Kenttä
199650a567 Don't access ESP before showing the menu 2023-09-10 15:02:58 +03:00
Lauri Kenttä
31172f71ca Add support for quiet setup 2023-09-10 15:02:58 +03:00
Lauri Kenttä
990f245ac9 Convert images when installing 2023-09-09 17:55:44 +03:00
Lauri Kenttä
dfadf67a21 Configure before installing 2023-09-09 17:55:44 +03:00
Lauri Kenttä
691fbd164b Improve path handling for platform independency 2023-09-09 17:55:39 +03:00
Lauri Kenttä
7d5f4eac17 Makefile default target: efi, setup and zip 2023-08-27 13:05:19 +03:00
Lauri Kenttä
0b5899e801 Make GIT_DESCRIBE.data const 2023-08-27 13:05:08 +03:00
Lauri Kenttä
4cf12f26b2 Update Print function signature for gnu-efi 3.0.11 2020-03-01 06:03:17 +02:00
Lauri Kenttä
3396a4799d Update change log and tag v1.5.1 2018-08-11 14:01:48 +03:00
Lauri Kenttä
d35a9abb0a Add debug=0 to the default config.txt 2018-08-11 13:45:04 +03:00
Lauri Kenttä
85811d62a6 Add note about EFI System Partition to config.txt 2018-08-11 13:42:41 +03:00
Lauri Kenttä
449dc6acc6 Create OsIndications if it's missing 2018-07-19 19:10:01 +03:00
Lauri Kenttä
1980e5c05c Clarify default config.txt 2018-07-19 19:10:01 +03:00
Lauri Kenttä
9a59f69a28 Compile C# with csc (not mcs) 2018-07-11 16:59:27 +03:00
Lauri Kenttä
1fffbcff2c Fix member naming 2018-07-11 16:55:26 +03:00
Soheibooo
bd7a5f3eea Correct typo in README 2018-03-02 00:45:10 -05:00
Lauri Kenttä
32643fab96 Update change log and tag v1.5.0 2017-09-30 22:41:00 +03:00
Lauri Kenttä
ecbca09419 Update README: clearer installation instructions 2017-09-30 22:41:00 +03:00
Lauri Kenttä
fa0f846f79 Implement rebooting to UEFI setup 2017-09-30 22:41:00 +03:00
Lauri Kenttä
b469b600ba Reorder the setup process a bit 2017-09-30 22:41:00 +03:00
Lauri Kenttä
f7fa54cfee Move Secure Boot handling to another function 2017-09-30 21:23:12 +03:00
Lauri Kenttä
e092c4768c Add throwable for graceful exiting 2017-09-30 16:41:25 +03:00
Lauri Kenttä
7ad4762a3d Improve ESP detection and move to another file 2017-09-30 16:40:17 +03:00
Lauri Kenttä
cea656631a Reorder some BootLoaderInfo code 2017-09-30 15:40:28 +03:00
Lauri Kenttä
1b6b17ec9a Move some helpers to SetupHelper class 2017-09-30 15:39:52 +03:00
Lauri Kenttä
91aad3b971 Makefile: Add variable for C# file list 2017-09-25 21:18:46 +03:00
Lauri Kenttä
cbcb630697 Update change log and tag v1.4.0 2017-08-29 02:16:22 +03:00
Lauri Kenttä
8531c728e8 Add some recovery instructions 2017-08-29 01:56:58 +03:00
Lauri Kenttä
f255b13027 Hard-code a fallback boot loader path
If the config is invalid, try to load the default boot loader backup.
2017-08-29 01:56:58 +03:00
Lauri Kenttä
054f8cc751 Convert config.txt to UTF-8 2017-08-29 01:56:58 +03:00
Lauri Kenttä
24c4a8aa0c Support UTF-8 in config.txt 2017-08-29 01:56:58 +03:00
Lauri Kenttä
b3cc80b37b Fix possible buffer overflow 2017-08-29 01:56:58 +03:00
Lauri Kenttä
c01cf121a0 Makefile: Add zip target for easier releases 2017-08-29 01:56:58 +03:00
Lauri Kenttä
c12bd7a859 Makefile: Set version to 'unknown' if git fails 2017-01-07 14:03:43 +02:00
Lauri Kenttä
ce44c3dcb3 Makefile: Build both IA-32 and x86_64 by default 2017-01-07 14:02:51 +02:00
Lauri Kenttä
3b0253f6fc Update change log and tag v1.3.0 2016-12-22 21:46:06 +02:00
Lauri Kenttä
8921bafa90 Check Secure Boot status before installing 2016-12-22 21:43:50 +02:00
13 changed files with 1774 additions and 453 deletions

View File

@@ -2,6 +2,54 @@
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

View File

@@ -9,19 +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)
GIT_DESCRIBE = $(shell git describe --tags)
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 default
all: efi setup zip
efi: bootx64.efi bootia32.efi
setup: setup.exe
default: bootx64.efi
all: bootx64.efi bootia32.efi 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: src/Setup.cs $(FILES_C) $(FILES_H)
echo 'public class GIT_DESCRIBE { public static string data = "$(GIT_DESCRIBE)"; }' > $@
src/GIT_DESCRIBE.cs: $(FILES_CS) $(FILES_C) $(FILES_H)
echo 'public class GIT_DESCRIBE { public const string data = "$(GIT_DESCRIBE)"; }' > $@
setup.exe: src/Setup.cs src/GIT_DESCRIBE.cs
mcs -define:GIT_DESCRIBE -out:$@ $^
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
@@ -32,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 bootia32.efi config.txt splash.bmp setup.exe README.md README.efilib LICENSE
tar cJf $@ --transform=s,^,HackBGRT/, $^

View File

@@ -4,45 +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 Secure Boot is disabled, or learn to sign EFI applications.
* Simple Windows installation:
* Get the latest release.
* Start `setup.exe` and select `I` (install).
* The installer will launch Notepad for modifying the configuration.
* If you need only one custom image, the defaults are fine.
* Otherwise, check out the examples in the configuration file.
* The installer will launch Paint for creating the image(s).
* You can create multiple images by using Save As.
* Be sure to always use the 24-bit BMP/DIB format.
* If Windows later restores the original boot loader, simply reinstall.
* If you wish to change the image or other configuration, simply reinstall.
* Installation for Windows with another boot loader (e.g. GRUB):
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`.
* Set `boot=\EFI\Microsoft\Boot\bootmgfw.efi` in `config.txt`.
* Point your boot loader to `\EFI\HackBGRT\bootx64.efi`.
* Installation for all operating systems:
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`.
* Set `boot=` to your preferred boot loader in `config.txt`.
* Set `\EFI\HackBGRT\bootx64.efi` as your default boot loader with `efibootmgr` or some other EFI boot manager tool.
* Make sure that 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

Binary file not shown.

469
src/Efi.cs Normal file
View 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
View 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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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,8 +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;
};

View File

@@ -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.
@@ -43,14 +43,16 @@ static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) {
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;
}
UINTN best_i = gop->Mode->Mode;
int best_w = gop->Mode->Info->HorizontalResolution;
int best_h = gop->Mode->Info->VerticalResolution;
w = (w <= 0 ? w < 0 ? best_w : 0x7fffffff : w);
h = (h <= 0 ? h < 0 ? best_h : 0x7fffffff : h);
int best_w = config.old_resolution_x = gop->Mode->Info->HorizontalResolution;
int best_h = config.old_resolution_y = gop->Mode->Info->VerticalResolution;
w = (w <= 0 ? w < 0 ? best_w : 999999 : w);
h = (h <= 0 ? h < 0 ? best_h : 999999 : h);
Debug(L"Looking for resolution %dx%d...\n", w, h);
for (UINT32 i = gop->Mode->MaxMode; i--;) {
@@ -86,29 +88,13 @@ static void SetResolution(int w, int 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);
}
}
/**
* Select the correct coordinate (manual, automatic, native)
*
* @param value The configured coordinate value; has special values for automatic and native.
* @param automatic The automatically calculated alternative.
* @param native The original coordinate.
* @see enum HackBGRT_coordinate
*/
static int SelectCoordinate(int value, int automatic, int native) {
if (value == HackBGRT_coord_auto) {
return automatic;
}
if (value == HackBGRT_coord_native) {
return native;
}
return value;
}
/**
* Create a new XSDT with the given number of entries.
*
@@ -153,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)) {
@@ -163,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++) {
@@ -171,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) {
@@ -208,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.
*
@@ -216,33 +240,51 @@ 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;
}
/**
* The main logic for BGRT modification.
@@ -259,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) {
@@ -282,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;
@@ -301,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.
*/
@@ -355,40 +429,60 @@ EFI_STATUS EFIAPI EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
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.\n");
Print(L"Get a Windows install disk or a recovery disk to fix your boot.\n");
#ifdef GIT_DESCRIBE
Print(L"HackBGRT version: " GIT_DESCRIBE L"\n");
#else
Print(L"HackBGRT version: unknown; not an official release?\n");
#endif
Print(L"Press any key to exit.\n");
ReadKey();
Print(L"Press any key (or wait 15 seconds) to exit.\n");
ReadKey(15000);
return 1;
}
}

View File

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

View File

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

View File

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