mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-07 09:36:10 -08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbcb630697 | ||
|
|
8531c728e8 | ||
|
|
f255b13027 | ||
|
|
054f8cc751 | ||
|
|
24c4a8aa0c | ||
|
|
b3cc80b37b | ||
|
|
c01cf121a0 | ||
|
|
c12bd7a859 | ||
|
|
ce44c3dcb3 | ||
|
|
3b0253f6fc | ||
|
|
8921bafa90 | ||
|
|
0347a1d921 | ||
|
|
b78e5cd977 | ||
|
|
e1d51be11b | ||
|
|
19203eceed | ||
|
|
3da9e1818a | ||
|
|
b7fd08c978 | ||
|
|
ff838ec0f6 | ||
|
|
a627895bfb | ||
|
|
733acccc42 | ||
|
|
efdd91a6d8 | ||
|
|
dae1a9abce | ||
|
|
e9ccec0d76 | ||
|
|
51ccb0255e | ||
|
|
a633eeb781 | ||
|
|
b5006f7771 | ||
|
|
c49f0f6cbc | ||
|
|
8ed61047dd | ||
|
|
31323a5111 | ||
|
|
9891039b06 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
boot*.efi
|
||||
setup.exe
|
||||
src/GIT_DESCRIBE.cs
|
||||
html
|
||||
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 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
|
||||
|
||||
28
Makefile
28
Makefile
@@ -7,11 +7,30 @@ 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_C = src/main.c src/util.c src/types.c src/config.c
|
||||
FILES_H = $(wildcard src/*.h)
|
||||
GIT_DESCRIBE = $(firstword $(shell git describe --tags) unknown)
|
||||
CFLAGS += '-DGIT_DESCRIBE=L"$(GIT_DESCRIBE)"'
|
||||
ZIPDIR = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||
ZIP = $(ZIPDIR).zip
|
||||
|
||||
.PHONY: all
|
||||
all: bootx64.efi
|
||||
efi: bootx64.efi bootia32.efi
|
||||
setup: setup.exe
|
||||
all: efi setup
|
||||
|
||||
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)"; }' > $@
|
||||
|
||||
setup.exe: src/Setup.cs src/GIT_DESCRIBE.cs
|
||||
mcs -define:GIT_DESCRIBE -out:$@ $^
|
||||
|
||||
bootx64.efi: CC_PREFIX = x86_64-w64-mingw32
|
||||
bootx64.efi: GNUEFI_ARCH = x86_64
|
||||
@@ -22,6 +41,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/, $^
|
||||
|
||||
28
README.md
28
README.md
@@ -11,21 +11,24 @@ When booting on a UEFI-based computer, Windows may show a vendor-defined logo wh
|
||||
**Important:** If you mess up the installation, your system may become unbootable! Create a rescue disk before use. This software comes with no warranty. Use at your own risk.
|
||||
|
||||
* Make sure that your computer is booting with UEFI.
|
||||
* Make sure that you have 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.
|
||||
* 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):
|
||||
* Copy the mentioned files to `[EFI System Partition]\EFI\HackBGRT\`.
|
||||
* 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:
|
||||
* Copy the mentioned files to `[EFI System Partition]\EFI\HackBGRT\`.
|
||||
* 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.
|
||||
|
||||
@@ -41,6 +44,13 @@ The image must be a 24-bit BMP file with a 54-byte header. That's a TrueColor BM
|
||||
|
||||
Multiple images may be specified, in which case one is picked at random.
|
||||
|
||||
## Recovery
|
||||
|
||||
If something breaks and you can't boot to Windows, you have the following options:
|
||||
|
||||
* Windows installation (or recovery) media can fix boot issues.
|
||||
* You can copy `[EFI System Partition]\EFI\HackBGRT\bootmgfw-original.efi` into `[EFI System Partition]\EFI\Microsoft\Boot\bootmgfw.efi` by some other means such as Linux or Windows command prompt.
|
||||
|
||||
## Building
|
||||
|
||||
* Compiler: GCC targeting w64-mingw32
|
||||
|
||||
BIN
config.txt
BIN
config.txt
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
|
||||
431
src/Setup.cs
Normal file
431
src/Setup.cs
Normal file
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using Microsoft.Win32;
|
||||
|
||||
/**
|
||||
* HackBGRT Setup.
|
||||
*/
|
||||
public class Setup {
|
||||
/**
|
||||
* The custom exception class for expected exceptions.
|
||||
*/
|
||||
public class SetupException: Exception {
|
||||
public SetupException(string msg): base(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EFI System Partition mounter.
|
||||
*/
|
||||
public class ESP {
|
||||
/** EFI System Partition mount point. */
|
||||
public string Path = null;
|
||||
|
||||
/** Was the ESP mounted by this instance? */
|
||||
private bool Mounted = false;
|
||||
|
||||
/**
|
||||
* Mount the EFI System Partition, or determine an existing mount point.
|
||||
*/
|
||||
public void Mount() {
|
||||
try {
|
||||
// Match "The EFI System Partition is mounted at E:\" with some language support.
|
||||
var re = new Regex(" EFI[^\n]*(?:\n[ \t]*)?([A-Z]:)\\\\");
|
||||
Path = re.Match(Execute("mountvol", "", false)).Groups[1].Captures[0].Value;
|
||||
return;
|
||||
} catch {
|
||||
}
|
||||
// Try to mount somewhere.
|
||||
for (char c = 'A'; c <= 'Z'; ++c) {
|
||||
if (Execute("mountvol", c + ": /S", true) != null) {
|
||||
Console.WriteLine("The EFI System Partition is mounted in " + c + ":\\");
|
||||
Path = c + ":";
|
||||
Mounted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new SetupException("The EFI System Partition is not mounted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmount the EFI System Partition, if it was previously mounted by this instance.
|
||||
*/
|
||||
public void Unmount() {
|
||||
if (Mounted) {
|
||||
Execute("mountvol", Path + " /D", true);
|
||||
Mounted = false;
|
||||
Path = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot loader type (MS/HackBGRT).
|
||||
*/
|
||||
public enum BootLoaderType {
|
||||
Unknown,
|
||||
MS,
|
||||
HackBGRT
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a boot loader: type and architecture.
|
||||
*/
|
||||
public struct BootLoaderInfo {
|
||||
public string Path;
|
||||
public bool Exists;
|
||||
public BootLoaderType Type;
|
||||
public string Arch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognize the boot loader type (MS/HackBGRT) and architecture.
|
||||
*
|
||||
* @param path Path to the boot loader.
|
||||
* @return Information about the boot loader.
|
||||
*/
|
||||
public static BootLoaderInfo RecognizeLoader(string path) {
|
||||
BootLoaderInfo info;
|
||||
info.Path = path;
|
||||
info.Exists = false;
|
||||
info.Type = BootLoaderType.Unknown;
|
||||
info.Arch = null;
|
||||
try {
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
info.Exists = true;
|
||||
string tmp = System.Text.Encoding.ASCII.GetString(data);
|
||||
if (tmp.IndexOf("HackBGRT") >= 0) {
|
||||
info.Type = BootLoaderType.HackBGRT;
|
||||
} else if (tmp.IndexOf("Microsoft Corporation") >= 0) {
|
||||
info.Type = BootLoaderType.MS;
|
||||
}
|
||||
switch (BitConverter.ToUInt16(data, BitConverter.ToInt32(data, 0x3c) + 4)) {
|
||||
case 0x014c: info.Arch = "ia32"; break;
|
||||
case 0x0200: info.Arch = "ia64"; break;
|
||||
case 0x8664: info.Arch = "x64"; break;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup the original boot loader.
|
||||
*
|
||||
* @param hackbgrt Path to the HackBGRT directory on the ESP.
|
||||
* @param msloader Path of the MS boot loader to backup.
|
||||
* @return Information about the boot loader backup.
|
||||
*/
|
||||
public static BootLoaderInfo Backup(string hackbgrt, string msloader) {
|
||||
try {
|
||||
if (!Directory.Exists(hackbgrt)) {
|
||||
Directory.CreateDirectory(hackbgrt);
|
||||
}
|
||||
BootLoaderInfo info = RecognizeLoader(msloader);
|
||||
if (info.Type == BootLoaderType.HackBGRT) {
|
||||
Console.WriteLine(msloader + " already contains a version of HackBGRT, skipping backup.");
|
||||
} else if (info.Type == BootLoaderType.MS) {
|
||||
// Overwrite any previous backup, the file might have changed.
|
||||
Copy(msloader, hackbgrt + "\\bootmgfw-original.efi");
|
||||
} else {
|
||||
Console.WriteLine(msloader + " doesn't look right, skipping backup.");
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
BootLoaderInfo msbackup = RecognizeLoader(hackbgrt + "\\bootmgfw-original.efi");
|
||||
if (!msbackup.Exists) {
|
||||
throw new SetupException("Couldn't backup the original bootmgfw.efi.");
|
||||
}
|
||||
if (msbackup.Arch == null || msbackup.Type != BootLoaderType.MS) {
|
||||
throw new SetupException("The boot loader backup (" + msbackup.Path + ") doesn't look like a supported MS boot loader");
|
||||
}
|
||||
return msbackup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install HackBGRT, copy files and replace the msloader with our own.
|
||||
*
|
||||
* @param src Path to the installation files.
|
||||
* @param hackbgrt Path to the HackBGRT directory on the ESP.
|
||||
* @param msloader Path of the MS boot loader to replace.
|
||||
* @param msbackup Information of the boot loader backup.
|
||||
*/
|
||||
public static void Install(string src, string hackbgrt, string msloader, BootLoaderInfo msbackup) {
|
||||
string loaderName = "boot" + msbackup.Arch + ".efi";
|
||||
string loaderSrc = src + "\\" + loaderName;
|
||||
string loaderInst = hackbgrt + "\\" + loaderName;
|
||||
if (!File.Exists(loaderSrc)) {
|
||||
throw new SetupException(loaderName + " doesn't exist.");
|
||||
}
|
||||
Copy(loaderSrc, loaderInst);
|
||||
if (!File.Exists(hackbgrt + "\\config.txt")) {
|
||||
Copy(src + "\\config.txt", hackbgrt + "\\config.txt");
|
||||
}
|
||||
if (!File.Exists(hackbgrt + "\\splash.bmp")) {
|
||||
Copy(src + "\\splash.bmp", hackbgrt + "\\splash.bmp");
|
||||
}
|
||||
Enable(hackbgrt, msloader, msbackup);
|
||||
Configure(hackbgrt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable HackBGRT, replace the msloader with our own.
|
||||
*
|
||||
* @param hackbgrt Path to the HackBGRT directory on the ESP.
|
||||
* @param msloader Path of the MS boot loader to replace.
|
||||
* @param msbackup Information of the boot loader backup.
|
||||
*/
|
||||
public static void Enable(string hackbgrt, string msloader, BootLoaderInfo msbackup) {
|
||||
string loaderName = "boot" + msbackup.Arch + ".efi";
|
||||
string loaderInst = hackbgrt + "\\" + loaderName;
|
||||
if (!File.Exists(loaderInst)) {
|
||||
throw new SetupException(loaderInst + " doesn't exist.");
|
||||
}
|
||||
if (!Copy(loaderInst, msloader)) {
|
||||
Copy(msbackup.Path, msloader);
|
||||
throw new SetupException("Couldn't install the new bootmgfw.efi.");
|
||||
}
|
||||
Console.WriteLine("HackBGRT is now enabled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure HackBGRT.
|
||||
*
|
||||
* @param hackbgrt Path to the HackBGRT directory on the ESP.
|
||||
*/
|
||||
public static void Configure(string hackbgrt) {
|
||||
// Open config.txt in notepad.
|
||||
Console.WriteLine("Check the configuration in " + hackbgrt + "\\config.txt.");
|
||||
Console.WriteLine("Use the supplied config.txt as reference.");
|
||||
Console.WriteLine("Be sure to check for any format changes if updating!");
|
||||
try {
|
||||
StartProcess("notepad", hackbgrt + "\\config.txt").WaitForExit();
|
||||
} catch {
|
||||
Console.WriteLine("Editing config.txt with notepad failed! Edit it manually.");
|
||||
}
|
||||
|
||||
// Open splash.bmp in mspaint.
|
||||
Console.WriteLine("Draw or copy your preferred image to splash.bmp.");
|
||||
try {
|
||||
StartProcess("mspaint", hackbgrt + "\\splash.bmp").WaitForExit();
|
||||
} catch {
|
||||
Console.WriteLine("Editing splash.bmp with mspaint failed! Edit it manually.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable HackBGRT, restore the original boot loader.
|
||||
*
|
||||
* @param hackbgrt Where HackBGRT is installed.
|
||||
* @param msloader Where to restore the MS boot loader.
|
||||
*/
|
||||
public static void Disable(string hackbgrt, string msloader) {
|
||||
BootLoaderInfo info = RecognizeLoader(msloader);
|
||||
if (info.Type == BootLoaderType.MS) {
|
||||
Console.WriteLine(msloader + " is already ok.");
|
||||
} else {
|
||||
if (!File.Exists(hackbgrt + "\\bootmgfw-original.efi")) {
|
||||
throw new SetupException("Missing the original bootmgfw.efi.");
|
||||
}
|
||||
if (!Copy(hackbgrt + "\\bootmgfw-original.efi", msloader)) {
|
||||
throw new SetupException("Failed to restore the original bootmgfw.efi.");
|
||||
}
|
||||
Console.WriteLine(msloader + " has been restored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall HackBGRT, restore the original boot loader.
|
||||
*
|
||||
* @param hackbgrt Where HackBGRT is installed.
|
||||
* @param msloader Where to restore the MS boot loader.
|
||||
*/
|
||||
public static void Uninstall(string hackbgrt, string msloader) {
|
||||
Disable(hackbgrt, msloader);
|
||||
try {
|
||||
Directory.Delete(hackbgrt, true);
|
||||
Console.WriteLine("HackBGRT has been removed.");
|
||||
} catch {
|
||||
Console.WriteLine("The directory " + hackbgrt + " couldn't be removed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the setup.
|
||||
*
|
||||
* @param src Path to the installation files.
|
||||
*/
|
||||
protected static void RunSetup(string src) {
|
||||
ESP esp = new ESP();
|
||||
try {
|
||||
esp.Mount();
|
||||
int secureBoot = -1;
|
||||
try {
|
||||
secureBoot = (int) Registry.GetValue(
|
||||
"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
|
||||
"UEFISecureBootEnabled",
|
||||
-1
|
||||
);
|
||||
} catch (Exception) {
|
||||
}
|
||||
if (secureBoot != 0) {
|
||||
if (secureBoot == 1) {
|
||||
Console.WriteLine("Secure Boot is enabled.");
|
||||
Console.WriteLine("HackBGRT doesn't work with Secure Boot.");
|
||||
Console.WriteLine("If you install HackBGRT, your machine may become unbootable,");
|
||||
Console.WriteLine("unless you manually disable Secure Boot.");
|
||||
} else {
|
||||
Console.WriteLine("Could not determine Secure Boot status.");
|
||||
Console.WriteLine("Your system may be incompatible with HackBGRT.");
|
||||
Console.WriteLine("If you install HackBGRT, your machine may become unbootable.");
|
||||
}
|
||||
Console.WriteLine("Do you still wish to continue? [Y/N]");
|
||||
var k = Console.ReadKey().Key;
|
||||
Console.WriteLine();
|
||||
if (k == ConsoleKey.Y) {
|
||||
Console.WriteLine("Continuing. THIS IS DANGEROUS!");
|
||||
} else if (k == ConsoleKey.N) {
|
||||
Console.WriteLine("Aborting.");
|
||||
return;
|
||||
} else {
|
||||
throw new SetupException("Invalid choice!");
|
||||
}
|
||||
}
|
||||
string hackbgrt = esp.Path + "\\EFI\\HackBGRT";
|
||||
string msloader = esp.Path + "\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
|
||||
if (!Directory.Exists(hackbgrt)) {
|
||||
Install(src, hackbgrt, msloader, Backup(hackbgrt, msloader));
|
||||
} else {
|
||||
Console.WriteLine("Choose action (press a key):");
|
||||
Console.WriteLine(" I = install, upgrade, fix installation");
|
||||
Console.WriteLine(" C = configure (edit config.txt and splash.bmp)");
|
||||
Console.WriteLine(" E = enable (after disabling); if in doubt, choose 'I' instead");
|
||||
Console.WriteLine(" D = disable, restore the original boot loader");
|
||||
Console.WriteLine(" R = remove completely; fully delete " + hackbgrt);
|
||||
var k = Console.ReadKey().Key;
|
||||
Console.WriteLine();
|
||||
if (k == ConsoleKey.I) {
|
||||
Install(src, hackbgrt, msloader, Backup(hackbgrt, msloader));
|
||||
} else if (k == ConsoleKey.C) {
|
||||
Configure(hackbgrt);
|
||||
} else if (k == ConsoleKey.E) {
|
||||
Enable(hackbgrt, msloader, Backup(hackbgrt, msloader));
|
||||
} else if (k == ConsoleKey.D) {
|
||||
Disable(hackbgrt, msloader);
|
||||
} else if (k == ConsoleKey.R) {
|
||||
Uninstall(hackbgrt, msloader);
|
||||
} else {
|
||||
throw new SetupException("Invalid choice!");
|
||||
}
|
||||
}
|
||||
} catch (SetupException e) {
|
||||
Console.WriteLine("Error: {0}", e.Message);
|
||||
Environment.ExitCode = 1;
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine("Unexpected error!\n{0}", e.ToString());
|
||||
Console.WriteLine("If this is the most current release, please report this bug.");
|
||||
Environment.ExitCode = 1;
|
||||
} finally {
|
||||
esp.Unmount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start another process.
|
||||
*
|
||||
* @param app Path to the application.
|
||||
* @param args The argument string.
|
||||
* @return The started process.
|
||||
*/
|
||||
protected static Process StartProcess(string app, string args) {
|
||||
try {
|
||||
var info = new ProcessStartInfo(app, args);
|
||||
info.UseShellExecute = false;
|
||||
return Process.Start(info);
|
||||
} catch {
|
||||
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.
|
||||
*/
|
||||
protected static string Execute(string app, string args, bool nullOnFail) {
|
||||
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();
|
||||
return (nullOnFail && p.ExitCode != 0) ? null : output;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file, overwrite by default.
|
||||
*
|
||||
* @param src The source path.
|
||||
* @param dest The destination path.
|
||||
* @return True, if the file was successfully copied.
|
||||
*/
|
||||
public static bool Copy(string src, string dest) {
|
||||
try {
|
||||
File.Copy(src, dest, true);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Main program.
|
||||
*
|
||||
* @param args The arguments.
|
||||
*/
|
||||
public static void Main(string[] args) {
|
||||
#if GIT_DESCRIBE
|
||||
Console.WriteLine("HackBGRT installer version: {0}", GIT_DESCRIBE.data);
|
||||
#else
|
||||
Console.WriteLine("HackBGRT installer version: unknown; not an official release?");
|
||||
#endif
|
||||
var self = Assembly.GetExecutingAssembly().Location;
|
||||
try {
|
||||
// Relaunch as admin, if needed.
|
||||
var id = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(id);
|
||||
var admin = WindowsBuiltInRole.Administrator;
|
||||
if (!principal.IsInRole(admin) && !args.Contains("no-elevate")) {
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo(self);
|
||||
startInfo.Arguments = "no-elevate";
|
||||
startInfo.Verb = "runas";
|
||||
startInfo.UseShellExecute = true;
|
||||
Process p = Process.Start(startInfo);
|
||||
p.WaitForExit();
|
||||
Environment.ExitCode = p.ExitCode;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
Console.WriteLine("This installer needs to be run as administrator!");
|
||||
Console.WriteLine("Press any key to quit.");
|
||||
Console.ReadKey();
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
RunSetup(Path.GetDirectoryName(self));
|
||||
Console.WriteLine("Press any key to quit.");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
68
src/config.c
68
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;
|
||||
@@ -65,7 +106,7 @@ static void ReadConfigImage(struct HackBGRT_config* config, const CHAR16* line)
|
||||
} else if (StrStr(line, L"remove")) {
|
||||
action = HackBGRT_REMOVE;
|
||||
} else if (StrStr(line, L"black")) {
|
||||
action = HackBGRT_BLACK;
|
||||
action = HackBGRT_REPLACE;
|
||||
} else if (StrStr(line, L"keep")) {
|
||||
action = HackBGRT_KEEP;
|
||||
} else {
|
||||
@@ -76,6 +117,17 @@ static void ReadConfigImage(struct HackBGRT_config* config, const CHAR16* line)
|
||||
SetBMPWithRandom(config, weight, action, ParseCoordinate(x, action), ParseCoordinate(y, action), 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) {
|
||||
line = TrimLeft(line);
|
||||
if (line[0] == L'#' || line[0] == 0) {
|
||||
@@ -98,5 +150,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);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Possible actions to perform on the BGRT.
|
||||
*/
|
||||
enum HackBGRT_action {
|
||||
HackBGRT_KEEP = 0, HackBGRT_REPLACE, HackBGRT_REMOVE, HackBGRT_BLACK
|
||||
HackBGRT_KEEP = 0, HackBGRT_REPLACE, HackBGRT_REMOVE
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,6 +28,8 @@ struct HackBGRT_config {
|
||||
int image_x;
|
||||
int image_y;
|
||||
int image_weight_sum;
|
||||
int resolution_x;
|
||||
int resolution_y;
|
||||
const CHAR16* boot_path;
|
||||
};
|
||||
|
||||
|
||||
358
src/main.c
358
src/main.c
@@ -34,6 +34,63 @@ static EFI_GRAPHICS_OUTPUT_PROTOCOL* GOP(void) {
|
||||
return gop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set screen resolution. If there is no exact match, try to find a bigger one.
|
||||
*
|
||||
* @param w Horizontal resolution. 0 for max, -1 for current.
|
||||
* @param h Vertical resolution. 0 for max, -1 for current.
|
||||
*/
|
||||
static void SetResolution(int w, int h) {
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = GOP();
|
||||
if (!gop) {
|
||||
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);
|
||||
|
||||
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);
|
||||
if (best_i != gop->Mode->Mode) {
|
||||
gop->SetMode(gop, best_i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the correct coordinate (manual, automatic, native)
|
||||
*
|
||||
@@ -53,106 +110,77 @@ static int SelectCoordinate(int value, int automatic, int native) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize (clear) a BGRT.
|
||||
* Create a new XSDT with the given number of entries.
|
||||
*
|
||||
* @param bgrt The BGRT to initialize.
|
||||
* @param xsdt0 The old XSDT.
|
||||
* @param entries The number of SDT entries.
|
||||
* @return Pointer to a new XSDT.
|
||||
*/
|
||||
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));
|
||||
ACPI_SDT_HEADER* CreateXsdt(ACPI_SDT_HEADER* xsdt0, UINTN entries) {
|
||||
ACPI_SDT_HEADER* xsdt = 0;
|
||||
UINT32 xsdt_len = sizeof(ACPI_SDT_HEADER) + entries * sizeof(UINT64);
|
||||
BS->AllocatePool(EfiACPIReclaimMemory, xsdt_len, (void**)&xsdt);
|
||||
if (!xsdt) {
|
||||
Print(L"HackBGRT: Failed to allocate memory for XSDT.\n");
|
||||
return 0;
|
||||
}
|
||||
ZeroMem(xsdt, xsdt_len);
|
||||
CopyMem(xsdt, xsdt0, min(xsdt0->length, xsdt_len));
|
||||
xsdt->length = xsdt_len;
|
||||
SetAcpiSdtChecksum(xsdt);
|
||||
return xsdt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill a BGRT as specified by the parameters.
|
||||
* Update the ACPI tables as needed for the desired BGRT change.
|
||||
*
|
||||
* @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.
|
||||
* If action is REMOVE, all BGRT entries will be removed.
|
||||
* If action is KEEP, the first BGRT entry will be returned.
|
||||
* If action is REPLACE, the given BGRT entry will be stored in each XSDT.
|
||||
*
|
||||
* @param action The intended action.
|
||||
* @param bgrt The BGRT, if action is REPLACE.
|
||||
* @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;
|
||||
|
||||
static ACPI_BGRT* HandleAcpiTables(enum HackBGRT_action action, ACPI_BGRT* bgrt) {
|
||||
for (int i = 0; i < ST->NumberOfTableEntries; i++) {
|
||||
EFI_GUID Acpi20TableGuid = ACPI_20_TABLE_GUID;
|
||||
EFI_GUID* vendor_guid = &ST->ConfigurationTable[i].VendorGuid;
|
||||
if (!CompareGuid(vendor_guid, &AcpiTableGuid) && !CompareGuid(vendor_guid, &Acpi20TableGuid)) {
|
||||
continue;
|
||||
}
|
||||
EFI_CONFIGURATION_TABLE *ect = &ST->ConfigurationTable[i];
|
||||
if (CompareMem(ect->VendorTable, "RSD PTR ", 8) != 0) {
|
||||
ACPI_20_RSDP* rsdp = (ACPI_20_RSDP *) ST->ConfigurationTable[i].VendorTable;
|
||||
if (CompareMem(rsdp->signature, "RSD PTR ", 8) != 0 || rsdp->revision < 2 || !VerifyAcpiRsdp2Checksums(rsdp)) {
|
||||
continue;
|
||||
}
|
||||
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");
|
||||
if (!xsdt || CompareMem(xsdt->signature, "XSDT", 4) != 0 || !VerifyAcpiSdtChecksum(xsdt)) {
|
||||
Debug(L"* XSDT: missing or invalid\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);
|
||||
|
||||
int bgrt_count = 0;
|
||||
for (int j = 0; j < entry_arr_length; j++) {
|
||||
ACPI_SDT_HEADER *entry = (ACPI_SDT_HEADER *)((UINTN)entry_arr[j]);
|
||||
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));
|
||||
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");
|
||||
switch (action) {
|
||||
case HackBGRT_KEEP:
|
||||
if (!bgrt) {
|
||||
Debug(L" -> Returning this one for later use.\n");
|
||||
bgrt = (ACPI_BGRT*) entry;
|
||||
}
|
||||
break;
|
||||
case HackBGRT_REMOVE:
|
||||
Debug(L" -> Deleting.\n");
|
||||
for (int k = j+1; k < entry_arr_length; ++k) {
|
||||
entry_arr[k-1] = entry_arr[k];
|
||||
}
|
||||
@@ -160,56 +188,36 @@ static ACPI_BGRT* FindBGRT(enum HackBGRT_action action) {
|
||||
entry_arr[entry_arr_length] = 0;
|
||||
xsdt->length -= sizeof(entry_arr[0]);
|
||||
--j;
|
||||
}
|
||||
break;
|
||||
case HackBGRT_REPLACE:
|
||||
Debug(L" -> Replacing.\n");
|
||||
entry_arr[j] = (UINTN) bgrt;
|
||||
}
|
||||
bgrt_count += 1;
|
||||
}
|
||||
}
|
||||
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;
|
||||
if (!bgrt_count && action == HackBGRT_REPLACE && bgrt) {
|
||||
Debug(L" - Adding missing BGRT.\n");
|
||||
xsdt = CreateXsdt(xsdt, entry_arr_length + 1);
|
||||
entry_arr = (UINT64*)&xsdt[1];
|
||||
entry_arr[entry_arr_length++] = (UINTN) bgrt;
|
||||
rsdp->xsdt_address = (UINTN) xsdt;
|
||||
SetAcpiRsdp2Checksums(rsdp);
|
||||
}
|
||||
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);
|
||||
SetAcpiSdtChecksum(xsdt);
|
||||
}
|
||||
return bgrt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a bitmap or generate one, or return 0 if not applicable.
|
||||
* Load a bitmap or generate a black one.
|
||||
*
|
||||
* @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.
|
||||
* @param path The BMP path within the root directory; NULL for a black BMP.
|
||||
* @return The loaded BMP, or 0 if not available.
|
||||
*/
|
||||
static BMP* LoadBMP(enum HackBGRT_action action, EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
static BMP* LoadBMP(EFI_FILE_HANDLE root_dir, const CHAR16* path) {
|
||||
BMP* bmp = 0;
|
||||
if (action == HackBGRT_KEEP || action == HackBGRT_REMOVE) {
|
||||
return 0;
|
||||
}
|
||||
if (action == HackBGRT_BLACK) {
|
||||
if (!path) {
|
||||
BS->AllocatePool(EfiBootServicesData, 58, (void**) &bmp);
|
||||
if (!bmp) {
|
||||
Print(L"HackBGRT: Failed to allocate a blank BMP!\n");
|
||||
@@ -226,10 +234,6 @@ static BMP* LoadBMP(enum HackBGRT_action action, EFI_FILE_HANDLE root_dir, const
|
||||
);
|
||||
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) {
|
||||
@@ -240,6 +244,85 @@ static BMP* LoadBMP(enum HackBGRT_action action, EFI_FILE_HANDLE root_dir, const
|
||||
return bmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main logic for BGRT modification.
|
||||
*
|
||||
* @param root_dir The root directory for loading a BMP.
|
||||
*/
|
||||
void HackBgrt(EFI_FILE_HANDLE root_dir) {
|
||||
// REMOVE: simply delete all BGRT entries.
|
||||
if (config.action == HackBGRT_REMOVE) {
|
||||
HandleAcpiTables(config.action, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Missing BGRT?
|
||||
if (!bgrt) {
|
||||
// Keep missing = do nothing.
|
||||
if (config.action == HackBGRT_KEEP) {
|
||||
return;
|
||||
}
|
||||
// Replace missing = allocate new.
|
||||
BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*bgrt), (void**)&bgrt);
|
||||
if (!bgrt) {
|
||||
Print(L"HackBGRT: Failed to allocate memory for BGRT.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the BGRT.
|
||||
const char data[0x38] =
|
||||
"BGRT" "\x38\x00\x00\x00" "\x00" "\xd6" "Mtblx*" "HackBGRT"
|
||||
"\x20\x17\x00\x00" "PTL " "\x02\x00\x00\x00"
|
||||
"\x01\x00" "\x00" "\x00";
|
||||
CopyMem(bgrt, data, sizeof(data));
|
||||
|
||||
// Get the image (either old or new).
|
||||
BMP* new_bmp = old_bmp;
|
||||
if (config.action == HackBGRT_REPLACE) {
|
||||
new_bmp = LoadBMP(root_dir, config.image_path);
|
||||
}
|
||||
|
||||
// No image = no need for BGRT.
|
||||
if (!new_bmp) {
|
||||
HandleAcpiTables(HackBGRT_REMOVE, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
bgrt->image_address = (UINTN) new_bmp;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Store this BGRT in the ACPI tables.
|
||||
SetAcpiSdtChecksum(bgrt);
|
||||
HandleAcpiTables(HackBGRT_REPLACE, bgrt);
|
||||
}
|
||||
|
||||
/**
|
||||
* The main program.
|
||||
*/
|
||||
@@ -269,34 +352,57 @@ EFI_STATUS EFIAPI EfiMain(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *ST_) {
|
||||
}
|
||||
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);
|
||||
}
|
||||
SetResolution(config.resolution_x, config.resolution_y);
|
||||
HackBgrt(root_dir);
|
||||
|
||||
EFI_HANDLE next_image_handle = 0;
|
||||
if (!config.boot_path) {
|
||||
Print(L"HackBGRT: Boot path not specified.\n");
|
||||
goto fail;
|
||||
} else {
|
||||
Debug(L"HackBGRT: Loading application %s.\n", config.boot_path);
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, (CHAR16*) config.boot_path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &next_image_handle))) {
|
||||
Print(L"HackBGRT: Failed to load application %s.\n", config.boot_path);
|
||||
}
|
||||
}
|
||||
|
||||
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 (!next_image_handle) {
|
||||
static CHAR16 default_boot_path[] = L"\\EFI\\HackBGRT\\bootmgfw-original.efi";
|
||||
Debug(L"HackBGRT: Loading application %s.\n", default_boot_path);
|
||||
EFI_DEVICE_PATH* boot_dp = FileDevicePath(image->DeviceHandle, default_boot_path);
|
||||
if (EFI_ERROR(BS->LoadImage(0, image_handle, boot_dp, 0, 0, &next_image_handle))) {
|
||||
Print(L"HackBGRT: Also failed to load application %s.\n", default_boot_path);
|
||||
goto fail;
|
||||
}
|
||||
Print(L"HackBGRT: Reverting to %s.\n", default_boot_path);
|
||||
Print(L"Press escape to cancel, any other key to boot.\n");
|
||||
if (ReadKey().ScanCode == SCAN_ESC) {
|
||||
goto fail;
|
||||
}
|
||||
config.boot_path = default_boot_path;
|
||||
}
|
||||
if (config.debug) {
|
||||
Print(L"HackBGRT: Ready to boot.\nPress escape to cancel, any other key to boot.\n");
|
||||
if (ReadKey().ScanCode == SCAN_ESC) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (EFI_ERROR(BS->StartImage(next_image_handle, 0, 0))) {
|
||||
Print(L"HackBGRT: 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);
|
||||
goto fail;
|
||||
|
||||
fail: {
|
||||
Print(L"HackBGRT has failed. Use parameter debug=1 for details.\nPress any key to exit.\n");
|
||||
WaitKey();
|
||||
Print(L"HackBGRT has failed. Use parameter debug=1 for details.\n");
|
||||
Print(L"Get a Windows install disk or a recovery disk to fix your boot.\n");
|
||||
#ifdef GIT_DESCRIBE
|
||||
Print(L"HackBGRT version: " GIT_DESCRIBE L"\n");
|
||||
#else
|
||||
Print(L"HackBGRT version: unknown; not an official release?\n");
|
||||
#endif
|
||||
Print(L"Press any key to exit.\n");
|
||||
ReadKey();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
37
src/types.c
Normal file
37
src/types.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include "types.h"
|
||||
|
||||
UINT8 SumBytes(const UINT8* arr, UINTN size) {
|
||||
UINT8 sum = 0;
|
||||
for (UINTN i = 0; i < size; ++i) {
|
||||
sum += arr[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
int VerifyAcpiRsdp2Checksums(const void* data) {
|
||||
const UINT8* arr = data;
|
||||
UINTN size = *(const UINT32*)&arr[20];
|
||||
return SumBytes(arr, 20) == 0 && SumBytes(arr, size) == 0;
|
||||
}
|
||||
|
||||
void SetAcpiRsdp2Checksums(void* data) {
|
||||
UINT8* arr = data;
|
||||
UINTN size = *(const UINT32*)&arr[20];
|
||||
arr[9] = 0;
|
||||
arr[32] = 0;
|
||||
arr[9] = -SumBytes(arr, 20);
|
||||
arr[32] = -SumBytes(arr, size);
|
||||
}
|
||||
|
||||
int VerifyAcpiSdtChecksum(const void* data) {
|
||||
const UINT8* arr = data;
|
||||
UINTN size = *(const UINT32*)&arr[4];
|
||||
return SumBytes(arr, size) == 0;
|
||||
}
|
||||
|
||||
void SetAcpiSdtChecksum(void* data) {
|
||||
UINT8* arr = data;
|
||||
UINTN size = *(const UINT32*)&arr[4];
|
||||
arr[9] = 0;
|
||||
arr[9] = -SumBytes(arr, size);
|
||||
}
|
||||
33
src/types.h
33
src/types.h
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <efi.h>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
/** RSDP (Root System Description Pointer) */
|
||||
@@ -51,4 +53,35 @@ typedef struct {
|
||||
UINT16 planes;
|
||||
UINT16 bpp;
|
||||
} BMP;
|
||||
|
||||
/**
|
||||
* Verify the checksums of an ACPI RSDP version 2.
|
||||
*
|
||||
* @param data Pointer to the table.
|
||||
* @return 1 if the checksum is correct, 0 otherwise.
|
||||
*/
|
||||
extern int VerifyAcpiRsdp2Checksums(const void* data);
|
||||
|
||||
/**
|
||||
* Set the correct checksums of an ACPI RSDP version 2.
|
||||
*
|
||||
* @param data Pointer to the table.
|
||||
*/
|
||||
extern void SetAcpiRsdp2Checksums(void* data);
|
||||
|
||||
/**
|
||||
* Verify the checksum of an ACPI SDT.
|
||||
*
|
||||
* @param data Pointer to the table.
|
||||
* @return 1 if the checksum is correct, 0 otherwise.
|
||||
*/
|
||||
extern int VerifyAcpiSdtChecksum(const void* data);
|
||||
|
||||
/**
|
||||
* Set the correct checksum for an ACPI SDT.
|
||||
*
|
||||
* @param data Pointer to the table.
|
||||
*/
|
||||
extern void SetAcpiSdtChecksum(void* data);
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
20
src/util.c
20
src/util.c
@@ -18,15 +18,6 @@ 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') {
|
||||
@@ -82,6 +73,13 @@ void WaitKey(void) {
|
||||
WaitForSingleEvent(ST->ConIn->WaitForKey, 0);
|
||||
}
|
||||
|
||||
EFI_INPUT_KEY ReadKey(void) {
|
||||
WaitKey();
|
||||
EFI_INPUT_KEY key = {0};
|
||||
ST->ConIn->ReadKeyStroke(ST->ConIn, &key);
|
||||
return key;
|
||||
}
|
||||
|
||||
void* LoadFileWithPadding(EFI_FILE_HANDLE dir, const CHAR16* path, UINTN* size_ptr, UINTN padding) {
|
||||
EFI_STATUS e;
|
||||
EFI_FILE_HANDLE handle;
|
||||
@@ -102,7 +100,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);
|
||||
|
||||
25
src/util.h
25
src/util.h
@@ -16,15 +16,6 @@ extern const CHAR16* TmpStr(CHAR8 *src, int length);
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -32,6 +23,13 @@ static inline int max(int a, int b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the smaller of two numbers.
|
||||
*/
|
||||
static inline int min(int a, int b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim BOM, spaces and tabs from the beginning of a string.
|
||||
*
|
||||
@@ -83,10 +81,17 @@ extern void RandomSeed(UINT64 a, UINT64 b);
|
||||
extern void RandomSeedAuto(void);
|
||||
|
||||
/**
|
||||
* Wait for a key press.
|
||||
* Wait for a key press. It will still remain in the buffer.
|
||||
*/
|
||||
extern void WaitKey(void);
|
||||
|
||||
/**
|
||||
* Wait for a key press and read it.
|
||||
*
|
||||
* @return The pressed key.
|
||||
*/
|
||||
extern EFI_INPUT_KEY ReadKey(void);
|
||||
|
||||
/**
|
||||
* 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