mirror of
https://github.com/Metabolix/HackBGRT.git
synced 2025-12-06 17:15:42 -08:00
Refactor EFI boot entry code into a new class
This commit is contained in:
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ GNUEFI_INC = gnu-efi/inc
|
|||||||
|
|
||||||
FILES_C = src/main.c src/util.c src/types.c src/config.c src/sbat.c src/efi.c
|
FILES_C = src/main.c src/util.c src/types.c src/config.c src/sbat.c src/efi.c
|
||||||
FILES_H = $(wildcard src/*.h)
|
FILES_H = $(wildcard src/*.h)
|
||||||
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs
|
FILES_CS = src/Setup.cs src/Esp.cs src/Efi.cs src/EfiBootEntries.cs
|
||||||
GIT_DESCRIBE := $(firstword $(GIT_DESCRIBE) $(shell git describe --tags) unknown)
|
GIT_DESCRIBE := $(firstword $(GIT_DESCRIBE) $(shell git describe --tags) unknown)
|
||||||
CFLAGS += '-DGIT_DESCRIBE_W=L"$(GIT_DESCRIBE)"' '-DGIT_DESCRIBE="$(GIT_DESCRIBE)"'
|
CFLAGS += '-DGIT_DESCRIBE_W=L"$(GIT_DESCRIBE)"' '-DGIT_DESCRIBE="$(GIT_DESCRIBE)"'
|
||||||
RELEASE_NAME = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
RELEASE_NAME = HackBGRT-$(GIT_DESCRIBE:v%=%)
|
||||||
|
|||||||
257
src/Efi.cs
257
src/Efi.cs
@@ -64,88 +64,6 @@ public class Efi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about an EFI boot entry.
|
|
||||||
*/
|
|
||||||
public class BootEntryData {
|
|
||||||
public UInt32 Attributes;
|
|
||||||
public string Label;
|
|
||||||
public class DevicePathNode {
|
|
||||||
public byte Type, SubType;
|
|
||||||
public byte[] Data;
|
|
||||||
public DevicePathNode(byte[] data) {
|
|
||||||
Type = data[0];
|
|
||||||
SubType = data[1];
|
|
||||||
Data = data.Skip(4).ToArray();
|
|
||||||
}
|
|
||||||
public byte[] ToBytes() {
|
|
||||||
var len = Data.Length + 4;
|
|
||||||
return new byte[] { Type, SubType, (byte)(len & 0xff), (byte)(len >> 8) }.Concat(Data).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public List<DevicePathNode> DevicePathNodes;
|
|
||||||
public byte[] Arguments;
|
|
||||||
|
|
||||||
public BootEntryData(byte[] data) {
|
|
||||||
Attributes = BitConverter.ToUInt32(data, 0);
|
|
||||||
var pathNodesLength = BitConverter.ToUInt16(data, 4);
|
|
||||||
Label = new string(BytesToUInt16s(data).Skip(3).TakeWhile(i => i != 0).Select(i => (char)i).ToArray());
|
|
||||||
var pos = 6 + 2 * (Label.Length + 1);
|
|
||||||
var pathNodesEnd = pos + pathNodesLength;
|
|
||||||
DevicePathNodes = new List<DevicePathNode>();
|
|
||||||
Arguments = new byte[0];
|
|
||||||
while (pos + 4 <= pathNodesEnd) {
|
|
||||||
var len = BitConverter.ToUInt16(data, pos + 2);
|
|
||||||
if (len < 4 || pos + len > pathNodesEnd) {
|
|
||||||
return; // throw new Exception("Bad entry.");
|
|
||||||
}
|
|
||||||
var node = new DevicePathNode(data.Skip(pos).Take(len).ToArray());
|
|
||||||
DevicePathNodes.Add(node);
|
|
||||||
if (node.Type == 0x7f && node.SubType == 0xff) {
|
|
||||||
// End of entire device path.
|
|
||||||
// Apparently some firmwares produce paths with unused nodes at the end.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += len;
|
|
||||||
}
|
|
||||||
Arguments = data.Skip(pathNodesEnd).ToArray();
|
|
||||||
}
|
|
||||||
public byte[] ToBytes() {
|
|
||||||
return new byte[0]
|
|
||||||
.Concat(BitConverter.GetBytes((UInt32) Attributes))
|
|
||||||
.Concat(BitConverter.GetBytes((UInt16) DevicePathNodes.Sum(n => n.Data.Length + 4)))
|
|
||||||
.Concat(Encoding.Unicode.GetBytes(Label + "\0"))
|
|
||||||
.Concat(DevicePathNodes.SelectMany(n => n.ToBytes()))
|
|
||||||
.Concat(Arguments)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
public DevicePathNode FileNameNode {
|
|
||||||
get {
|
|
||||||
var d = DevicePathNodes;
|
|
||||||
return d.Count > 1 && d[d.Count - 1].Type == 0x7F && d[d.Count - 2].Type == 0x04 ? d[d.Count - 2] : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool HasFileName {
|
|
||||||
get {
|
|
||||||
return FileNameNode != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string FileName {
|
|
||||||
get {
|
|
||||||
if (!HasFileName) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return new string(Encoding.Unicode.GetChars(FileNameNode.Data).TakeWhile(c => c != '\0').ToArray());
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if (!HasFileName) {
|
|
||||||
throw new Exception("Logic error: Setting FileName on a bad boot entry.");
|
|
||||||
}
|
|
||||||
FileNameNode.Data = Encoding.Unicode.GetBytes(value + "\0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GUID of the global EFI variables.
|
* GUID of the global EFI variables.
|
||||||
*/
|
*/
|
||||||
@@ -206,7 +124,7 @@ public class Efi {
|
|||||||
* @param guid GUID of the EFI variable.
|
* @param guid GUID of the EFI variable.
|
||||||
* @return Information about the EFI variable.
|
* @return Information about the EFI variable.
|
||||||
*/
|
*/
|
||||||
private static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
public static Variable GetVariable(string name, string guid = EFI_GLOBAL_GUID) {
|
||||||
Variable result = new Variable();
|
Variable result = new Variable();
|
||||||
result.Name = name;
|
result.Name = name;
|
||||||
result.Guid = guid;
|
result.Guid = guid;
|
||||||
@@ -255,7 +173,7 @@ public class Efi {
|
|||||||
* @param v Information of the variable.
|
* @param v Information of the variable.
|
||||||
* @param dryRun Don't actually set the variable.
|
* @param dryRun Don't actually set the variable.
|
||||||
*/
|
*/
|
||||||
private static void SetVariable(Variable v, bool dryRun = false) {
|
public static void SetVariable(Variable v, bool dryRun = false) {
|
||||||
Setup.WriteLine($"Writing EFI variable {v.Name} (see log for details)");
|
Setup.WriteLine($"Writing EFI variable {v.Name} (see log for details)");
|
||||||
Setup.Log($"Writing EFI variable: {v}");
|
Setup.Log($"Writing EFI variable: {v}");
|
||||||
if (dryRun) {
|
if (dryRun) {
|
||||||
@@ -341,177 +259,6 @@ public class Efi {
|
|||||||
return Enumerable.Range(0, bytes.Length / 2).Select(i => (UInt16) (bytes[2 * i] + 0x100 * bytes[2 * i + 1]));
|
return Enumerable.Range(0, bytes.Length / 2).Select(i => (UInt16) (bytes[2 * i] + 0x100 * bytes[2 * i + 1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable the said boot entry from BootOrder.
|
|
||||||
*
|
|
||||||
* @param label Label of the boot entry.
|
|
||||||
* @param fileName File name of the boot entry.
|
|
||||||
* @param dryRun Don't actually disable the entry.
|
|
||||||
* @return True, if the entry was found in BootOrder.
|
|
||||||
*/
|
|
||||||
public static bool DisableBootEntry(string label, string fileName, bool dryRun = false) {
|
|
||||||
Variable bootOrder;
|
|
||||||
try {
|
|
||||||
bootOrder = GetVariable("BootOrder");
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bootOrder.Data == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Setup.Log($"Read EFI variable: {bootOrder}");
|
|
||||||
var found = false;
|
|
||||||
var bootOrderInts = new List<UInt16>();
|
|
||||||
foreach (var num in BytesToUInt16s(bootOrder.Data)) {
|
|
||||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
|
||||||
if (entry.Data != null) {
|
|
||||||
var entryData = new BootEntryData(entry.Data);
|
|
||||||
if (entryData.Label == label && entryData.FileName == fileName) {
|
|
||||||
found = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bootOrderInts.Add(num);
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
bootOrder.Data = bootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
|
||||||
SetVariable(bootOrder, dryRun);
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and enable the said boot entry from BootOrder.
|
|
||||||
*
|
|
||||||
* @param label Label of the boot entry.
|
|
||||||
* @param fileName File name of the boot entry.
|
|
||||||
* @param alwaysCopyFromMS If true, do not preserve any existing data.
|
|
||||||
* @param dryRun Don't actually create the entry.
|
|
||||||
*/
|
|
||||||
public static void MakeAndEnableBootEntry(string label, string fileName, bool alwaysCopyFromMS, bool dryRun = false) {
|
|
||||||
Variable msEntry = null, ownEntry = null;
|
|
||||||
UInt16 msNum = 0, ownNum = 0;
|
|
||||||
|
|
||||||
// Find a free entry and the MS bootloader entry.
|
|
||||||
Variable bootOrder = null;
|
|
||||||
try {
|
|
||||||
bootOrder = GetVariable("BootOrder");
|
|
||||||
} catch {
|
|
||||||
if (dryRun) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bootOrder == null || bootOrder.Data == null) {
|
|
||||||
throw new Exception("MakeBootEntry: Could not read BootOrder. Maybe your computer is defective.");
|
|
||||||
}
|
|
||||||
var bootCurrent = GetVariable("BootCurrent");
|
|
||||||
if (bootCurrent.Data == null) {
|
|
||||||
throw new Exception("MakeBootEntry: Could not read BootCurrent. Maybe your computer is defective.");
|
|
||||||
}
|
|
||||||
Setup.Log($"Read EFI variable: {bootOrder}");
|
|
||||||
Setup.Log($"Read EFI variable: {bootCurrent}");
|
|
||||||
var bootOrderInts = new List<UInt16>(BytesToUInt16s(bootOrder.Data));
|
|
||||||
foreach (var num in BytesToUInt16s(bootCurrent.Data).Concat(bootOrderInts).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
|
||||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
|
||||||
if (entry.Data == null) {
|
|
||||||
// Use only Boot0000 .. Boot00FF because some firmwares expect that.
|
|
||||||
if (ownEntry == null && num < 0x100) {
|
|
||||||
ownNum = num;
|
|
||||||
ownEntry = entry;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var entryData = new BootEntryData(entry.Data);
|
|
||||||
if (!entryData.HasFileName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entryData.Label == label && entryData.FileName == fileName) {
|
|
||||||
ownNum = num;
|
|
||||||
ownEntry = entry;
|
|
||||||
}
|
|
||||||
if (msEntry == null && entryData.FileName.StartsWith("\\EFI\\Microsoft\\Boot\\bootmgfw.efi", StringComparison.OrdinalIgnoreCase)) {
|
|
||||||
msNum = num;
|
|
||||||
msEntry = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ownEntry != null && msEntry != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ownEntry == null) {
|
|
||||||
throw new Exception("MakeBootEntry: Boot entry list is full.");
|
|
||||||
} else if (msEntry == null) {
|
|
||||||
throw new Exception("MakeBootEntry: Windows Boot Manager not found.");
|
|
||||||
} else {
|
|
||||||
Setup.Log($"Read EFI variable: {msEntry}");
|
|
||||||
Setup.Log($"Read EFI variable: {ownEntry}");
|
|
||||||
// Make a new boot entry using the MS entry as a starting point.
|
|
||||||
var entryData = new BootEntryData(msEntry.Data);
|
|
||||||
if (!alwaysCopyFromMS && ownEntry.Data != null) {
|
|
||||||
entryData = new BootEntryData(ownEntry.Data);
|
|
||||||
// Shim expects the arguments to be a filename or nothing.
|
|
||||||
// But BCDEdit expects some Microsoft-specific data.
|
|
||||||
// Modify the entry so that BCDEdit still recognises it
|
|
||||||
// but the data becomes a valid UCS-2 / UTF-16LE file name.
|
|
||||||
var str = new string(entryData.Arguments.Take(12).Select(c => (char) c).ToArray());
|
|
||||||
if (str == "WINDOWS\0\x01\0\0\0") {
|
|
||||||
entryData.Arguments[8] = (byte) 'X';
|
|
||||||
} else if (str != "WINDOWS\0\x58\0\0\0") {
|
|
||||||
// Not recognized. Clear the arguments.
|
|
||||||
entryData.Arguments = new byte[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entryData.Arguments = new byte[0];
|
|
||||||
entryData.Label = label;
|
|
||||||
entryData.FileName = fileName;
|
|
||||||
}
|
|
||||||
entryData.Attributes = 1; // LOAD_OPTION_ACTIVE
|
|
||||||
ownEntry.Attributes = 7; // EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
|
|
||||||
ownEntry.Data = entryData.ToBytes();
|
|
||||||
SetVariable(ownEntry, dryRun);
|
|
||||||
}
|
|
||||||
|
|
||||||
var msPos = bootOrderInts.IndexOf(msNum);
|
|
||||||
var ownPos = bootOrderInts.IndexOf(ownNum);
|
|
||||||
var mustAdd = ownPos == -1;
|
|
||||||
var mustMove = 0 <= msPos && msPos <= ownPos;
|
|
||||||
if (mustAdd || mustMove) {
|
|
||||||
if (mustMove) {
|
|
||||||
bootOrderInts.RemoveAt(ownPos);
|
|
||||||
}
|
|
||||||
bootOrderInts.Insert(msPos < 0 ? 0 : msPos, ownNum);
|
|
||||||
bootOrder.Data = bootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
|
||||||
SetVariable(bootOrder, dryRun);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log the boot entries.
|
|
||||||
*/
|
|
||||||
public static void LogBootEntries() {
|
|
||||||
try {
|
|
||||||
var bootOrder = GetVariable("BootOrder");
|
|
||||||
var bootCurrent = GetVariable("BootCurrent");
|
|
||||||
Setup.Log($"LogBootOrder: {bootOrder}");
|
|
||||||
Setup.Log($"LogBootOrder: {bootCurrent}");
|
|
||||||
var bootOrderInts = new List<UInt16>(BytesToUInt16s(bootOrder.Data));
|
|
||||||
// Windows can't enumerate EFI variables, and trying them all is too slow.
|
|
||||||
// BootOrder + BootCurrent + the first 0xff entries should be enough.
|
|
||||||
var seen = new HashSet<UInt16>();
|
|
||||||
foreach (var num in bootOrderInts.Concat(BytesToUInt16s(bootCurrent.Data)).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
|
||||||
if (seen.Contains(num)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen.Add(num);
|
|
||||||
var entry = GetVariable(String.Format("Boot{0:X04}", num));
|
|
||||||
if (entry.Data != null) {
|
|
||||||
Setup.Log($"LogBootOrder: {entry}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Setup.Log($"LogBootOrder failed: {e.ToString()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve HackBGRT log collected during boot.
|
* Retrieve HackBGRT log collected during boot.
|
||||||
*/
|
*/
|
||||||
|
|||||||
309
src/EfiBootEntries.cs
Normal file
309
src/EfiBootEntries.cs
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for handling EFI boot entries. Lazy access with cache.
|
||||||
|
* Notice: Data is not updated after the first read.
|
||||||
|
*/
|
||||||
|
public class EfiBootEntries {
|
||||||
|
/**
|
||||||
|
* 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(Efi.BytesToUInt16s(data).Skip(3).TakeWhile(i => i != 0).Select(i => (char)i).ToArray());
|
||||||
|
var pos = 6 + 2 * (Label.Length + 1);
|
||||||
|
var pathNodesEnd = pos + pathNodesLength;
|
||||||
|
DevicePathNodes = new List<DevicePathNode>();
|
||||||
|
Arguments = new byte[0];
|
||||||
|
while (pos + 4 <= pathNodesEnd) {
|
||||||
|
var len = BitConverter.ToUInt16(data, pos + 2);
|
||||||
|
if (len < 4 || pos + len > pathNodesEnd) {
|
||||||
|
return; // throw new Exception("Bad entry.");
|
||||||
|
}
|
||||||
|
var node = new DevicePathNode(data.Skip(pos).Take(len).ToArray());
|
||||||
|
DevicePathNodes.Add(node);
|
||||||
|
if (node.Type == 0x7f && node.SubType == 0xff) {
|
||||||
|
// End of entire device path.
|
||||||
|
// Apparently some firmwares produce paths with unused nodes at the end.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
Arguments = data.Skip(pathNodesEnd).ToArray();
|
||||||
|
}
|
||||||
|
public byte[] ToBytes() {
|
||||||
|
return new byte[0]
|
||||||
|
.Concat(BitConverter.GetBytes((UInt32) Attributes))
|
||||||
|
.Concat(BitConverter.GetBytes((UInt16) DevicePathNodes.Sum(n => n.Data.Length + 4)))
|
||||||
|
.Concat(Encoding.Unicode.GetBytes(Label + "\0"))
|
||||||
|
.Concat(DevicePathNodes.SelectMany(n => n.ToBytes()))
|
||||||
|
.Concat(Arguments)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
public DevicePathNode FileNameNode {
|
||||||
|
get {
|
||||||
|
var d = DevicePathNodes;
|
||||||
|
return d.Count > 1 && d[d.Count - 1].Type == 0x7F && d[d.Count - 2].Type == 0x04 ? d[d.Count - 2] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool HasFileName {
|
||||||
|
get {
|
||||||
|
return FileNameNode != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string FileName {
|
||||||
|
get {
|
||||||
|
if (!HasFileName) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return new string(Encoding.Unicode.GetChars(FileNameNode.Data).TakeWhile(c => c != '\0').ToArray());
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if (!HasFileName) {
|
||||||
|
throw new Exception("Logic error: Setting FileName on a bad boot entry.");
|
||||||
|
}
|
||||||
|
FileNameNode.Data = Encoding.Unicode.GetBytes(value + "\0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the Windows boot loader.
|
||||||
|
*/
|
||||||
|
public const string WindowsLoaderPath = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path to the HackBGRT loader.
|
||||||
|
*/
|
||||||
|
public const string OwnLoaderPath = "\\EFI\\HackBGRT\\loader.efi";
|
||||||
|
|
||||||
|
private readonly Dictionary<UInt16, (Efi.Variable, BootEntryData)> cache;
|
||||||
|
private readonly Efi.Variable BootOrder;
|
||||||
|
private readonly Efi.Variable BootCurrent;
|
||||||
|
private readonly List<UInt16> BootOrderInts;
|
||||||
|
private readonly List<UInt16> BootCurrentInts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. Reads BootOrder and BootCurrent.
|
||||||
|
*/
|
||||||
|
public EfiBootEntries() {
|
||||||
|
cache = new Dictionary<UInt16, (Efi.Variable, BootEntryData)>();
|
||||||
|
BootOrder = Efi.GetVariable("BootOrder");
|
||||||
|
BootCurrent = Efi.GetVariable("BootCurrent");
|
||||||
|
if (BootOrder.Data == null || BootCurrent.Data == null) {
|
||||||
|
throw new Exception("Could not read BootOrder or BootCurrent.");
|
||||||
|
}
|
||||||
|
BootCurrentInts = new List<UInt16>(Efi.BytesToUInt16s(BootCurrent.Data));
|
||||||
|
BootOrderInts = new List<UInt16>(Efi.BytesToUInt16s(BootOrder.Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boot entry with the given number.
|
||||||
|
*
|
||||||
|
* @param num Number of the boot entry.
|
||||||
|
* @return The boot entry.
|
||||||
|
*/
|
||||||
|
public (Efi.Variable, BootEntryData) GetEntry(UInt16 num) {
|
||||||
|
if (!cache.ContainsKey(num)) {
|
||||||
|
var v = Efi.GetVariable(String.Format("Boot{0:X04}", num));
|
||||||
|
cache[num] = (v, v.Data == null ? null : new BootEntryData(v.Data));
|
||||||
|
}
|
||||||
|
return cache[num];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find entry by file name.
|
||||||
|
*
|
||||||
|
* @param fileName File name of the boot entry.
|
||||||
|
* @return The boot entry.
|
||||||
|
*/
|
||||||
|
public (UInt16, Efi.Variable, BootEntryData) FindEntry(string fileName) {
|
||||||
|
var rest = Enumerable.Range(0, 0xff).Select(i => (UInt16) i);
|
||||||
|
var entryAccessOrder = BootCurrentInts.Concat(BootOrderInts).Concat(rest);
|
||||||
|
foreach (var num in entryAccessOrder) {
|
||||||
|
var (v, e) = GetEntry(num);
|
||||||
|
if (fileName == null ? e == null : (e != null && e.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase))) {
|
||||||
|
return (num, v, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0xffff, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Windows boot entry.
|
||||||
|
*/
|
||||||
|
public (UInt16, Efi.Variable, BootEntryData) WindowsEntry {
|
||||||
|
get { return FindEntry(WindowsLoaderPath); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the HackBGRT boot entry.
|
||||||
|
*/
|
||||||
|
public (UInt16, Efi.Variable, BootEntryData) OwnEntry {
|
||||||
|
get { return FindEntry(OwnLoaderPath); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a free boot entry.
|
||||||
|
*/
|
||||||
|
public (UInt16, Efi.Variable, BootEntryData) FreeEntry {
|
||||||
|
get { return FindEntry(null); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the said boot entry from BootOrder.
|
||||||
|
*
|
||||||
|
* @param dryRun Don't actually write to NVRAM.
|
||||||
|
* @return True, if the entry was found in BootOrder.
|
||||||
|
*/
|
||||||
|
public bool DisableOwnEntry(bool dryRun = false) {
|
||||||
|
var (ownNum, ownVar, _) = OwnEntry;
|
||||||
|
if (ownVar == null) {
|
||||||
|
Setup.Log("Own entry not found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Setup.Log($"Old boot order: {BootOrder}");
|
||||||
|
if (!BootOrderInts.Contains(ownNum)) {
|
||||||
|
Setup.Log("Own entry not in BootOrder.");
|
||||||
|
} else {
|
||||||
|
Setup.Log($"Disabling own entry: {ownNum:X04}");
|
||||||
|
BootOrderInts.Remove(ownNum);
|
||||||
|
BootOrder.Data = BootOrderInts.SelectMany(num => new byte[] { (byte)(num & 0xff), (byte)(num >> 8) }).ToArray();
|
||||||
|
Efi.SetVariable(BootOrder, dryRun);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the boot entry.
|
||||||
|
*
|
||||||
|
* @param alwaysCopyFromMS If true, do not preserve any existing data.
|
||||||
|
* @param dryRun Don't actually write to NVRAM.
|
||||||
|
*/
|
||||||
|
public void MakeOwnEntry(bool alwaysCopyFromMS, bool dryRun = false) {
|
||||||
|
var (msNum, msVar, msEntry) = WindowsEntry;
|
||||||
|
if (msEntry == null) {
|
||||||
|
throw new Exception("MakeOwnEntry: Windows Boot Manager not found.");
|
||||||
|
}
|
||||||
|
var (ownNum, ownVar, ownEntry) = OwnEntry;
|
||||||
|
if (ownVar == null) {
|
||||||
|
(ownNum, ownVar, ownEntry) = FreeEntry;
|
||||||
|
if (ownVar == null) {
|
||||||
|
throw new Exception("MakeOwnEntry: No free entry.");
|
||||||
|
}
|
||||||
|
Setup.Log($"Creating own entry {ownNum:X4}.");
|
||||||
|
} else {
|
||||||
|
Setup.Log($"Updating own entry {ownNum:X4}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Setup.Log($"Read EFI variable: {msVar}");
|
||||||
|
Setup.Log($"Read EFI variable: {ownVar}");
|
||||||
|
// Make a new boot entry using the MS entry as a starting point.
|
||||||
|
if (!alwaysCopyFromMS && ownEntry != null) {
|
||||||
|
// Shim expects the arguments to be a filename or nothing.
|
||||||
|
// But BCDEdit expects some Microsoft-specific data.
|
||||||
|
// Modify the entry so that BCDEdit still recognises it
|
||||||
|
// but the data becomes a valid UCS-2 / UTF-16LE file name.
|
||||||
|
var str = new string(ownEntry.Arguments.Take(12).Select(c => (char) c).ToArray());
|
||||||
|
if (str == "WINDOWS\0\x01\0\0\0") {
|
||||||
|
ownEntry.Arguments[8] = (byte) 'X';
|
||||||
|
} else if (str != "WINDOWS\0\x58\0\0\0") {
|
||||||
|
// Not recognized. Clear the arguments.
|
||||||
|
ownEntry.Arguments = new byte[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ownEntry = msEntry;
|
||||||
|
ownEntry.Arguments = new byte[0];
|
||||||
|
ownEntry.Label = "HackBGRT";
|
||||||
|
ownEntry.FileName = OwnLoaderPath;
|
||||||
|
}
|
||||||
|
ownEntry.Attributes = 1; // LOAD_OPTION_ACTIVE
|
||||||
|
ownVar.Attributes = 7; // EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS
|
||||||
|
ownVar.Data = ownEntry.ToBytes();
|
||||||
|
Efi.SetVariable(ownVar, dryRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the own boot entry.
|
||||||
|
*
|
||||||
|
* @param dryRun Don't actually write to NVRAM.
|
||||||
|
*/
|
||||||
|
public void EnableOwnEntry(bool dryRun = false) {
|
||||||
|
var (ownNum, ownVar, _) = OwnEntry;
|
||||||
|
if (ownVar == null) {
|
||||||
|
Setup.Log("Own entry not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var (msNum, _, _) = WindowsEntry;
|
||||||
|
var msPos = BootOrderInts.IndexOf(msNum);
|
||||||
|
var ownPos = BootOrderInts.IndexOf(ownNum);
|
||||||
|
var mustAdd = ownPos == -1;
|
||||||
|
var mustMove = 0 <= msPos && msPos <= ownPos;
|
||||||
|
Setup.Log($"Old boot order: {BootOrder}");
|
||||||
|
if (mustAdd || mustMove) {
|
||||||
|
Setup.Log($"Enabling own entry: {ownNum:X04}");
|
||||||
|
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();
|
||||||
|
Efi.SetVariable(BootOrder, dryRun);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the boot entries.
|
||||||
|
*/
|
||||||
|
public void LogEntries() {
|
||||||
|
Setup.Log($"LogEntries: {BootOrder}");
|
||||||
|
Setup.Log($"LogEntries: {BootCurrent}");
|
||||||
|
// Windows can't enumerate EFI variables, and trying them all is too slow.
|
||||||
|
// BootOrder + BootCurrent + the first 0xff entries should be enough.
|
||||||
|
var seen = new HashSet<UInt16>();
|
||||||
|
foreach (var num in BootOrderInts.Concat(BootCurrentInts).Concat(Enumerable.Range(0, 0xff).Select(i => (UInt16) i))) {
|
||||||
|
if (seen.Contains(num)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.Add(num);
|
||||||
|
var (v, e) = GetEntry(num);
|
||||||
|
if (e != null) {
|
||||||
|
Setup.Log($"LogEntries: {v}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to log the boot entries.
|
||||||
|
*/
|
||||||
|
public static void TryLogEntries() {
|
||||||
|
try {
|
||||||
|
new EfiBootEntries().LogEntries();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Setup.Log($"LogEntries failed: {e.ToString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Setup.cs
18
src/Setup.cs
@@ -453,7 +453,7 @@ public class Setup {
|
|||||||
}
|
}
|
||||||
var guid = match.Value;
|
var guid = match.Value;
|
||||||
Execute("bcdedit", $"/set {guid} device partition={Esp.Location}", true);
|
Execute("bcdedit", $"/set {guid} device partition={Esp.Location}", true);
|
||||||
Execute("bcdedit", $"/set {guid} path \\EFI\\HackBGRT\\loader.efi", true);
|
Execute("bcdedit", $"/set {guid} path {EfiBootEntries.OwnLoaderPath}", true);
|
||||||
foreach (var arg in new string[] { "locale", "inherit", "default", "resumeobject", "displayorder", "toolsdisplayorder", "timeout" }) {
|
foreach (var arg in new string[] { "locale", "inherit", "default", "resumeobject", "displayorder", "toolsdisplayorder", "timeout" }) {
|
||||||
Execute("bcdedit", $"/deletevalue {guid} {arg}", true);
|
Execute("bcdedit", $"/deletevalue {guid} {arg}", true);
|
||||||
}
|
}
|
||||||
@@ -462,9 +462,11 @@ public class Setup {
|
|||||||
WriteLine("Enabled NVRAM entry for HackBGRT with BCDEdit.");
|
WriteLine("Enabled NVRAM entry for HackBGRT with BCDEdit.");
|
||||||
// Verify that the entry was created.
|
// Verify that the entry was created.
|
||||||
Execute("bcdedit", "/enum firmware", true);
|
Execute("bcdedit", "/enum firmware", true);
|
||||||
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", false, DryRun);
|
var e = new EfiBootEntries();
|
||||||
|
e.MakeOwnEntry(false, DryRun); // Fix load options for shim.
|
||||||
|
e.EnableOwnEntry(DryRun);
|
||||||
Execute("bcdedit", $"/enum {guid}", true);
|
Execute("bcdedit", $"/enum {guid}", true);
|
||||||
Efi.LogBootEntries();
|
e.LogEntries();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log($"EnableBCDEdit failed: {e.ToString()}");
|
Log($"EnableBCDEdit failed: {e.ToString()}");
|
||||||
throw new SetupException("Failed to enable HackBGRT with BCDEdit!");
|
throw new SetupException("Failed to enable HackBGRT with BCDEdit!");
|
||||||
@@ -508,10 +510,12 @@ public class Setup {
|
|||||||
* Enable HackBGRT boot entry.
|
* Enable HackBGRT boot entry.
|
||||||
*/
|
*/
|
||||||
protected void EnableEntry() {
|
protected void EnableEntry() {
|
||||||
Efi.MakeAndEnableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", true, DryRun);
|
var e = new EfiBootEntries();
|
||||||
|
e.MakeOwnEntry(true, DryRun);
|
||||||
|
e.EnableOwnEntry(DryRun);
|
||||||
WriteLine("Enabled NVRAM entry for HackBGRT.");
|
WriteLine("Enabled NVRAM entry for HackBGRT.");
|
||||||
// Verify that the entry was created.
|
// Verify that the entry was created.
|
||||||
Efi.LogBootEntries();
|
e.LogEntries();
|
||||||
Execute("bcdedit", "/enum firmware", true);
|
Execute("bcdedit", "/enum firmware", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +523,7 @@ public class Setup {
|
|||||||
* Disable HackBGRT boot entry.
|
* Disable HackBGRT boot entry.
|
||||||
*/
|
*/
|
||||||
protected void DisableEntry() {
|
protected void DisableEntry() {
|
||||||
Efi.DisableBootEntry("HackBGRT", "\\EFI\\HackBGRT\\loader.efi", DryRun);
|
new EfiBootEntries().DisableOwnEntry(DryRun);
|
||||||
WriteLine("Disabled NVRAM entry for HackBGRT.");
|
WriteLine("Disabled NVRAM entry for HackBGRT.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,7 +947,7 @@ public class Setup {
|
|||||||
var bootLog = $"\n--- BOOT LOG START ---\n{Efi.GetHackBGRTLog()}\n--- BOOT LOG END ---";
|
var bootLog = $"\n--- BOOT LOG START ---\n{Efi.GetHackBGRTLog()}\n--- BOOT LOG END ---";
|
||||||
Setup.Log(bootLog);
|
Setup.Log(bootLog);
|
||||||
Efi.LogBGRT();
|
Efi.LogBGRT();
|
||||||
Efi.LogBootEntries();
|
EfiBootEntries.TryLogEntries();
|
||||||
if (GetBootTime() is DateTime bootTime) {
|
if (GetBootTime() is DateTime bootTime) {
|
||||||
var configTime = new[] { File.GetCreationTime("config.txt"), File.GetLastWriteTime("config.txt") }.Max();
|
var configTime = new[] { File.GetCreationTime("config.txt"), File.GetLastWriteTime("config.txt") }.Max();
|
||||||
Log($"Boot time = {bootTime}, config.txt changed = {configTime}");
|
Log($"Boot time = {bootTime}, config.txt changed = {configTime}");
|
||||||
|
|||||||
Reference in New Issue
Block a user