diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..fdd2ff9 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,12 @@ +INPUT = src +FILE_PATTERNS = *.c *.h +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +EXTRACT_STATIC = YES +STRIP_CODE_COMMENTS = NO +INLINE_SOURCES = NO +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_DYNAMIC_SECTIONS = YES +GENERATE_LATEX = NO diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d72cf6 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC = $(CC_PREFIX)-gcc +CFLAGS = -std=c11 -O2 -ffreestanding -mno-red-zone -fno-stack-protector -Wshadow -Wall -Wunused -Werror-implicit-function-declaration -Werror +CFLAGS += -I$(GNUEFI_INC) -I$(GNUEFI_INC)/$(GNUEFI_ARCH) -I$(GNUEFI_INC)/protocol +LDFLAGS = -nostdlib -shared -Wl,-dll -Wl,--subsystem,10 -e _EfiMain +LIBS = -L$(GNUEFI_LIB) -lefi -lgcc + +GNUEFI_INC = /usr/$(CC_PREFIX)/include/efi +GNUEFI_LIB = /usr/$(CC_PREFIX)/lib + +FILES_C = src/main.c src/util.c src/config.c +FILES_H = $(wildcard src/*.h) + +.PHONY: all +all: bootx64.efi + +bootx64.efi: CC_PREFIX = x86_64-w64-mingw32 +bootx64.efi: GNUEFI_ARCH = x86_64 +bootx64.efi: $(FILES_C) + $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LIBS) -s + +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/, $^ diff --git a/README.efilib b/README.efilib new file mode 100644 index 0000000..0d43424 --- /dev/null +++ b/README.efilib @@ -0,0 +1,31 @@ +# vim: set fileencoding=utf-8 + +HackBGRT uses the gnu-efi library, which in turn is using the EFI Application +Toolkit distributed by Intel at http://developer.intel.com/technology/efi + +This code is covered by the following agreement: + +Copyright (c) 1998-2000 Intel Corporation + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and +the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions +and the following disclaimer in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. THE EFI SPECIFICATION AND ALL OTHER INFORMATION +ON THIS WEB SITE ARE PROVIDED "AS IS" WITH NO WARRANTIES, AND ARE SUBJECT +TO CHANGE WITHOUT NOTICE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..16c3756 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# HackBGRT + +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. + +## 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. + +## Configuration + +The configuration options are described in `config.txt`, which should be stored in `[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. + +Multiple images may be specified, in which case one is picked at random. + +## Building + +* Compiler: GCC targeting w64-mingw32 +* Compiler flags: see Makefile +* Libraries: gnu-efi diff --git a/config.txt b/config.txt new file mode 100755 index 0000000..5bb7962 Binary files /dev/null and b/config.txt differ diff --git a/install.bat b/install.bat new file mode 100755 index 0000000..cc1cc41 --- /dev/null +++ b/install.bat @@ -0,0 +1,120 @@ +@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 diff --git a/splash.bmp b/splash.bmp new file mode 100644 index 0000000..3046a1d Binary files /dev/null and b/splash.bmp differ diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..60e13a4 --- /dev/null +++ b/src/config.c @@ -0,0 +1,102 @@ +#include "config.h" +#include "util.h" + +#include + +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) { + Print(L"HackBGRT: Failed to load configuration (%s)!\n", path); + return FALSE; + } + UINTN str_len = str_bytes / sizeof(*str); + + for (int i = 0; i < str_len;) { + int j = i; + while (j < str_len && str[j] != '\r' && str[j] != '\n') { + ++j; + } + while (j < str_len && (str[j] == '\r' || str[j] == '\n')) { + str[j] = 0; + ++j; + } + ReadConfigLine(config, root_dir, &str[i]); + i = j; + } + // NOTICE: string is not freed, because paths are not copied. + return TRUE; +} + +static void SetBMPWithRandom(struct HackBGRT_config* config, int weight, enum HackBGRT_action action, int x, int y, const CHAR16* path) { + config->image_weight_sum += weight; + UINT32 random = Random(); + UINT32 limit = 0xfffffffful / config->image_weight_sum * weight; + if (config->debug) { + Print(L"HackBGRT: weight %d, action %d, x %d, y %d, path %s, random = %08x, limit = %08x\n", weight, action, x, y, path, random, limit); + } + if (!config->image_weight_sum || random <= limit) { + config->action = action; + config->image_path = path; + 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 && StrnCmp(str, L"native", 6) == 0) || action == HackBGRT_KEEP) { + return HackBGRT_coord_native; + } + return HackBGRT_coord_auto; +} + +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* f = StrStrAfter(line, L"path="); + enum HackBGRT_action action = HackBGRT_KEEP; + if (f) { + action = HackBGRT_REPLACE; + } else if (StrStr(line, L"remove")) { + action = HackBGRT_REMOVE; + } else if (StrStr(line, L"black")) { + action = HackBGRT_BLACK; + } else if (StrStr(line, L"keep")) { + action = HackBGRT_KEEP; + } else { + Print(L"HackBGRT: Invalid image line: %s\n", line); + return; + } + int weight = n && (!f || n < f) ? Atoi(n) : 1; + SetBMPWithRandom(config, weight, action, ParseCoordinate(x, action), ParseCoordinate(y, action), f); +} + +void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* line) { + line = TrimLeft(line); + if (line[0] == L'#' || line[0] == 0) { + return; + } + + if (StrnCmp(line, L"debug=", 6) == 0) { + config->debug = (StrCmp(line, L"debug=1") == 0); + return; + } + if (StrnCmp(line, L"image=", 6) == 0) { + ReadConfigImage(config, line + 6); + return; + } + if (StrnCmp(line, L"boot=", 5) == 0) { + config->boot_path = line + 5; + return; + } + if (StrnCmp(line, L"config=", 7) == 0) { + ReadConfigFile(config, root_dir, line + 7); + return; + } + Print(L"Unknown configuration directive: %s\n", line); +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..b8a0089 --- /dev/null +++ b/src/config.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +/** + * Possible actions to perform on the BGRT. + */ +enum HackBGRT_action { + HackBGRT_KEEP = 0, HackBGRT_REPLACE, HackBGRT_REMOVE, HackBGRT_BLACK +}; + +/** + * Special values for the image coordinates. + * @see struct HackBGRT_config + */ +enum HackBGRT_coordinate { + HackBGRT_coord_auto = 0x10000001, + HackBGRT_coord_native = 0x10000002 +}; + +/** + * The configuration. + */ +struct HackBGRT_config { + int debug; + enum HackBGRT_action action; + const CHAR16* image_path; + int image_x; + int image_y; + int image_weight_sum; + const CHAR16* boot_path; +}; + +/** + * Read a configuration parameter. (May recursively read config files.) + * + * @param config The configuration to modify. + * @param root_dir The root directory, in case the parameter contains an include. + * @param line The configuration line to parse. + */ +extern void ReadConfigLine(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* line); + +/** + * Read a configuration file. (May recursively read more files.) + * + * @param config The configuration to modify. + * @param root_dir The root directory. + * @param path The path to the file. + * @return FALSE, if the file couldn't be read, TRUE otherwise. + */ +extern BOOLEAN ReadConfigFile(struct HackBGRT_config* config, EFI_FILE_HANDLE root_dir, const CHAR16* path); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..3108ea5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,311 @@ +#include +#include + +#include "types.h" +#include "config.h" +#include "util.h" + +/** + * The Print function signature. + */ +typedef UINTN print_t(IN CHAR16 *fmt, ...); + +/** + * The function for debug printing; either Print or NullPrint. + */ +print_t* Debug = NullPrint; + +/** + * The configuration. + */ +static struct HackBGRT_config config = { + .action = HackBGRT_KEEP +}; + +/** + * Get the GOP (Graphics Output Protocol) pointer. + */ +static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) { + static EFI_GRAPHICS_OUTPUT_PROTOCOL* gop; + if (!gop) { + EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&gop); + } + return gop; +} + +/** + * 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; +} + +/** + * Initialize (clear) a BGRT. + * + * @param bgrt The BGRT to initialize. + */ +static void InitBGRT(ACPI_BGRT* 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)); +} + +/** + * Fill a BGRT as specified by the parameters. + * + * @param bgrt The BGRT to fill. + * @param new_bmp The BMP to use. + * @param new_x The x coordinate to use. + * @param new_y The y coordinate to use. + */ +static void FillBGRT(ACPI_BGRT* bgrt, BMP* new_bmp, int new_x, int new_y) { + BMP* old_bmp = (BMP*) (UINTN) bgrt->image_address; + ACPI_BGRT bgrt0 = *bgrt; + InitBGRT(bgrt); + + if (new_bmp) { + bgrt->image_address = (UINTN) new_bmp; + } + BMP* bmp = (BMP*) (UINTN) bgrt->image_address; + + // Calculate the automatically centered position for the image. + int x_auto, y_auto; + if (GOP()) { + x_auto = max(0, ((int)GOP()->Mode->Info->HorizontalResolution - (int)bmp->width) / 2); + y_auto = max(0, ((int)GOP()->Mode->Info->VerticalResolution * 2/3 - (int)bmp->height) / 2); + } else { + x_auto = max(0, (int)bgrt0.image_offset_x + ((int)old_bmp->width - (int)bmp->width) / 2); + y_auto = max(0, (int)bgrt0.image_offset_y + ((int)old_bmp->height - (int)bmp->height) / 2); + } + + // Set the position (manual, automatic, original). + bgrt->image_offset_x = SelectCoordinate(new_x, x_auto, bgrt0.image_offset_x); + bgrt->image_offset_y = SelectCoordinate(new_y, y_auto, bgrt0.image_offset_y); + Debug(L"HackBGRT: BMP at (%d, %d).\n", (int) bgrt->image_offset_x, (int) bgrt->image_offset_y); + + bgrt->header.checksum = 0; + bgrt->header.checksum = CalculateAcpiChecksum(bgrt, sizeof(*bgrt)); +} + +/** + * Find the BGRT and optionally destroy it or create if missing. + * + * @param action The intended action. + * @return Pointer to the BGRT, or 0 if not found (or destroyed). + */ +static ACPI_BGRT* FindBGRT(enum HackBGRT_action action) { + ACPI_20_RSDP* good_rsdp = 0; + ACPI_BGRT* bgrt = 0; + + for (int i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_GUID Acpi20TableGuid = ACPI_20_TABLE_GUID; + EFI_GUID* vendor_guid = &ST->ConfigurationTable[i].VendorGuid; + if (!CompareGuid(vendor_guid, &AcpiTableGuid) && !CompareGuid(vendor_guid, &Acpi20TableGuid)) { + continue; + } + EFI_CONFIGURATION_TABLE *ect = &ST->ConfigurationTable[i]; + if (CompareMem(ect->VendorTable, "RSD PTR ", 8) != 0) { + continue; + } + ACPI_20_RSDP* rsdp = (ACPI_20_RSDP *)ect->VendorTable; + Debug(L"RSDP: revision = %d, OEM ID = %s\n", rsdp->revision, TmpStr(rsdp->oem_id, 6)); + + if (rsdp->revision < 2) { + Debug(L"* XSDT: N/A (revision < 2)\n"); + continue; + } + ACPI_SDT_HEADER* xsdt = (ACPI_SDT_HEADER *) (UINTN) rsdp->xsdt_address; + if (!xsdt) { + Debug(L"* XSDT: N/A (null)\n"); + continue; + } + if (CompareMem(xsdt->signature, "XSDT", 4) != 0) { + Debug(L"* XSDT: N/A (invalid signature)\n"); + continue; + } + good_rsdp = rsdp; + 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); + + for (int j = 0; j < entry_arr_length; j++) { + ACPI_SDT_HEADER *entry = (ACPI_SDT_HEADER *)((UINTN)entry_arr[j]); + Debug(L" - ACPI table: %s, revision = %d, OEM ID = %s\n", TmpStr(entry->signature, 4), entry->revision, TmpStr(entry->oem_id, 6)); + if (CompareMem(entry->signature, "BGRT", 4) == 0) { + if (!bgrt && action != HackBGRT_REMOVE) { + bgrt = (void*) entry; + } else { + if (bgrt) { + Debug(L" -> Deleting; BGRT was already found!\n"); + } else { + Debug(L" -> Deleting.\n"); + } + for (int k = j+1; k < entry_arr_length; ++k) { + entry_arr[k-1] = entry_arr[k]; + } + --entry_arr_length; + entry_arr[entry_arr_length] = 0; + xsdt->length -= sizeof(entry_arr[0]); + --j; + } + } + } + } + if (action == HackBGRT_REMOVE) { + return 0; + } + if (!good_rsdp) { + Print(L"HackBGRT: RSDP or XSDT not found.\n"); + return 0; + } + if (!bgrt) { + if (action == HackBGRT_KEEP) { + Print(L"HackBGRT: BGRT not found.\n"); + return 0; + } + Debug(L"HackBGRT: BGRT not found, creating.\n"); + ACPI_20_RSDP* rsdp = good_rsdp; + ACPI_SDT_HEADER* xsdt0 = (ACPI_SDT_HEADER *) (UINTN) rsdp->xsdt_address; + ACPI_SDT_HEADER* xsdt = 0; + UINT32 xsdt_len = xsdt0->length + sizeof(UINT64); + BS->AllocatePool(EfiACPIReclaimMemory, xsdt_len, (void**)&xsdt); + BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*bgrt), (void**)&bgrt); + if (!xsdt || !bgrt) { + Print(L"HackBGRT: Failed to allocate memory for XSDT and BGRT.\n"); + return 0; + } + rsdp->xsdt_address = (UINTN) xsdt; + CopyMem(xsdt, xsdt0, xsdt0->length); + *(UINT64*)((char*)xsdt + xsdt->length) = (UINTN) bgrt; + xsdt->length = xsdt_len; + InitBGRT(bgrt); + } + return bgrt; +} + +/** + * Load a bitmap or generate one, or return 0 if not applicable. + * + * @param action Tells what to do. + * @param root_dir The root directory for loading a BMP. + * @param path The BMP path within the root directory. + * @return The loaded BMP, or 0 if not needed or not available. + */ +static BMP* LoadBMP(enum HackBGRT_action action, EFI_FILE_HANDLE root_dir, const CHAR16* path) { + BMP* bmp = 0; + if (action == HackBGRT_KEEP || action == HackBGRT_REMOVE) { + return 0; + } + if (action == HackBGRT_BLACK) { + 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; + } + if (!path) { + Print(L"HackBGRT: Missing BMP path. REPORT THIS BUG!"); + return 0; + } + 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; + } + return bmp; +} + +/** + * The main program. + */ +EFI_STATUS EFIAPI EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) { + InitializeLib(image_handle, ST_); + + EFI_LOADED_IMAGE* image; + if (EFI_ERROR(BS->HandleProtocol(image_handle, &LoadedImageProtocol, (void**) &image))) { + Debug(L"HackBGRT: LOADED_IMAGE_PROTOCOL failed.\n"); + goto fail; + } + + EFI_FILE_HANDLE root_dir = LibOpenRoot(image->DeviceHandle); + + CHAR16 **argv; + int argc = GetShellArgcArgv(image_handle, &argv); + + if (argc <= 1) { + const CHAR16* config_path = L"\\EFI\\HackBGRT\\config.txt"; + if (!ReadConfigFile(&config, root_dir, config_path)) { + Print(L"HackBGRT: No config, no command line!\n", config_path); + goto fail; + } + } + for (int i = 1; i < argc; ++i) { + ReadConfigLine(&config, root_dir, argv[i]); + } + Debug = config.debug ? Print : NullPrint; + + BMP* new_bmp = LoadBMP(config.action, root_dir, config.image_path); + ACPI_BGRT* bgrt = FindBGRT(config.action); + if (bgrt) { + FillBGRT(bgrt, new_bmp, config.image_x, config.image_y); + } + + if (!config.boot_path) { + Print(L"HackBGRT: Boot path not specified.\n"); + goto fail; + } + + Debug(L"HackBGRT: Loading and booting %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); + goto fail; + } + if (EFI_ERROR(BS->StartImage(next_image_handle, 0, 0))) { + Print(L"HackBGRT: StartImage for %s failed.\n", config.boot_path); + goto fail; + } + Print(L"HackBGRT: Started %s. Why are we still here?!\n", config.boot_path); + goto fail; + + fail: { + Print(L"HackBGRT has failed. Use parameter debug=1 for details.\nPress any key to exit.\n"); + WaitKey(); + return 1; + } +} + +/** + * Forward to EfiMain. + * + * Some compilers and architectures differ in underscore handling. This helps. + */ +EFI_STATUS EFIAPI _EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) { + return EfiMain(image_handle, ST_); +} diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..fb02b2c --- /dev/null +++ b/src/types.h @@ -0,0 +1,54 @@ +#pragma once + +#pragma pack(push, 1) + +/** RSDP (Root System Description Pointer) */ +typedef struct { + CHAR8 signature[8]; + UINT8 checksum; + CHAR8 oem_id[6]; + UINT8 revision; + UINT32 rsdt_address; + UINT32 length; + UINT64 xsdt_address; + UINT8 extended_checksum; + UINT8 reserved[3]; +} ACPI_20_RSDP; + +/** SDT (System Description Table) entry header */ +typedef struct { + CHAR8 signature[4]; + UINT32 length; + UINT8 revision; + UINT8 checksum; + CHAR8 oem_id[6]; + CHAR8 oem_table_id[8]; + UINT32 oem_revision; + UINT32 asl_compiler_id; + UINT32 asl_compiler_revision; +} ACPI_SDT_HEADER; + +/** BGRT structure */ +typedef struct { + ACPI_SDT_HEADER header; + UINT16 version; + UINT8 status; + UINT8 image_type; + UINT64 image_address; + UINT32 image_offset_x; + UINT32 image_offset_y; +} ACPI_BGRT; + +/** Bitmap file header */ +typedef struct { + UINT8 magic_BM[2]; + UINT32 file_size; + UINT8 unused_0x06[4]; + UINT32 pixel_data_offset; + UINT32 dib_header_size; + UINT32 width; + UINT32 height; + UINT16 planes; + UINT16 bpp; +} BMP; +#pragma pack(pop) diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..471ada8 --- /dev/null +++ b/src/util.c @@ -0,0 +1,115 @@ +#include "util.h" + +#include + +const CHAR16* TmpStr(CHAR8 *src, int length) { + static CHAR16 arr[4][16]; + static int j; + CHAR16* dest = arr[j = (j+1) % 4]; + int i; + for (i = 0; i < length && i < 16-1 && src[i]; ++i) { + dest[i] = src[i]; + } + dest[i] = 0; + return dest; +} + +UINTN NullPrint(IN CHAR16 *fmt, ...) { + return 0; +} + +UINT8 CalculateAcpiChecksum(void* data, UINTN size) { + UINT8 sum = 0; + UINT8* arr = data; + for (UINTN i = 0; i < size; ++i) { + sum += arr[i]; + } + return 256 - sum; +} + +const CHAR16* TrimLeft(const CHAR16* s) { + // Skip white-space and BOM. + while (s[0] == L'\xfeff' || s[0] == ' ' || s[0] == '\t') { + ++s; + } + return s; +} + +const CHAR16* StrStr(const CHAR16* haystack, const CHAR16* needle) { + int len = StrLen(needle); + while (haystack && haystack[0]) { + if (StrnCmp(haystack, needle, len) == 0) { + return haystack; + } + ++haystack; + } + return 0; +} + +const CHAR16* StrStrAfter(const CHAR16* haystack, const CHAR16* needle) { + return (haystack = StrStr(haystack, needle)) ? haystack + StrLen(needle) : 0; +} + +UINT64 Random_a, Random_b; + +UINT64 Random(void) { + // Implemented after xoroshiro128plus.c + if (!Random_a && !Random_b) { + RandomSeedAuto(); + } + UINT64 a = Random_a, b = Random_b, r = a + b; + b ^= a; + Random_a = rotl(a, 55) ^ b ^ (b << 14); + Random_b = rotl(b, 36); + return r; +} + +void RandomSeed(UINT64 a, UINT64 b) { + Random_a = a; + Random_b = b; +} + +void RandomSeedAuto(void) { + EFI_TIME t; + RT->GetTime(&t, 0); + UINT64 a, b = ((((((UINT64) t.Second * 100 + t.Minute) * 100 + t.Hour) * 100 + t.Day) * 100 + t.Month) * 10000 + t.Year) * 300000 + t.Nanosecond; + BS->GetNextMonotonicCount(&a); + RandomSeed(a, b), Random(), Random(); +} + +void WaitKey(void) { + ST->ConIn->Reset(ST->ConIn, FALSE); + WaitForSingleEvent(ST->ConIn->WaitForKey, 0); +} + +void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_ptr, UINTN padding) { + EFI_STATUS e; + EFI_FILE_HANDLE handle; + + e = dir->Open(dir, &handle, (CHAR16*) path, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(e)) { + return 0; + } + + EFI_FILE_INFO *info = LibFileInfo(handle); + UINTN size = info->FileSize; + FreePool(info); + + void* data = 0; + e = BS->AllocatePool(EfiBootServicesData, size + padding, &data); + if (EFI_ERROR(e)) { + handle->Close(handle); + return 0; + } + e = handle->Read(handle, &size, data); + *(UINT32*)((char*)data + size) = 0; + handle->Close(handle); + if (EFI_ERROR(e)) { + FreePool(data); + return 0; + } + if (size_ptr) { + *size_ptr = size; + } + return data; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..ee7d00a --- /dev/null +++ b/src/util.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +/** + * Convert a short ASCII string to UCS2, store in a static array. + * + * @param src The ASCII string. Will be truncated to 15 characters + null. + * @param length The maximum length, if the string is not null-terminated. + * @return The UCS2 string, statically allocated, null-terminated. + */ +extern const CHAR16* TmpStr(CHAR8 *src, int length); + +/** + * Empty function that has the same signature as Print. + */ +extern UINTN NullPrint(IN CHAR16 *fmt, ...); + +/** + * Calculate the checksum for an ACPI table. + * + * @param data Pointer to the table. + * @param size Table length in bytes. + * @return Checksum. + */ +extern UINT8 CalculateAcpiChecksum(void* data, UINTN size); + +/** + * Return the greater of two numbers. + */ +static inline int max(int a, int b) { + return a > b ? a : b; +} + +/** + * Trim BOM, spaces and tabs from the beginning of a string. + * + * @param s The string. + * @return Pointer to the first acceptable character. + */ +extern const CHAR16* TrimLeft(const CHAR16* s); + +/** + * Find the position of another string within a string. + * + * @param haystack The full text. + * @param needle The string to look for. + * @return Pointer to the first occurence of needle in the haystack, or 0. + */ +extern const CHAR16* StrStr(const CHAR16* haystack, const CHAR16* needle); + + +/** + * Find the position after another string within a string. + * + * @param haystack The full text. + * @param needle The string to look for. + * @return Pointer after the first occurence of needle in the haystack, or 0. + */ +extern const CHAR16* StrStrAfter(const CHAR16* haystack, const CHAR16* needle); + +/** + * Rotate left a 64-bit value. + */ +static inline UINT64 rotl(const UINT64 x, int k) { + return (x << k) | (x >> (64 - k)); +} + +/** + * Generate a random 64-bit number. + */ +extern UINT64 Random(void); + + +/** + * Seed the random number generator. Pass 0 and 0 to seed from the clock. + */ +extern void RandomSeed(UINT64 a, UINT64 b); + +/** + * Seed the random number generator automatically. + */ +extern void RandomSeedAuto(void); + +/** + * Wait for a key press. + */ +extern void WaitKey(void); + +/** + * Load a file, allocate some extra bytes as well. + */ +extern void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_ptr, UINTN padding); + +/** + * Load a file. + */ +static inline void* LoadFile(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_ptr) { + return LoadFileWithPadding(dir, path, size_ptr, 0); +} + diff --git a/uninstall.bat b/uninstall.bat new file mode 100755 index 0000000..c2fed88 --- /dev/null +++ b/uninstall.bat @@ -0,0 +1,9 @@ +@ECHO OFF +CD %~dp0 + +IF NOT EXIST install.bat ( + ECHO The uninstaller needs install.bat! + EXIT /B 1 +) + +CALL install.bat uninstall