Files
WPinternals/Models/GPT.cs
T
2018-10-25 22:35:49 +02:00

645 lines
29 KiB
C#

// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Serialization;
namespace WPinternals
{
[XmlType("Partitions")]
public class GPT
{
private byte[] GPTBuffer;
private UInt32 HeaderOffset;
private UInt32 HeaderSize;
private UInt32 TableOffset;
private UInt32 TableSize;
private UInt32 PartitionEntrySize;
private UInt32 MaxPartitions;
internal UInt64 FirstUsableSector;
internal UInt64 LastUsableSector;
internal bool HasChanged = false;
[XmlElement("Partition")]
public List<Partition> Partitions = new List<Partition>();
public GPT() // Only for serialization
{
}
internal GPT(byte[] GPTBuffer)
{
this.GPTBuffer = GPTBuffer;
UInt32? TempHeaderOffset = ByteOperations.FindAscii(GPTBuffer, "EFI PART");
if (TempHeaderOffset == null)
throw new WPinternalsException("Bad GPT");
HeaderOffset = (UInt32)TempHeaderOffset;
HeaderSize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x0C);
TableOffset = HeaderOffset + 0x200;
FirstUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x28);
LastUsableSector = ByteOperations.ReadUInt64(GPTBuffer, HeaderOffset + 0x30);
MaxPartitions = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x50);
PartitionEntrySize = ByteOperations.ReadUInt32(GPTBuffer, HeaderOffset + 0x54);
TableSize = MaxPartitions * PartitionEntrySize;
if ((TableOffset + TableSize) > GPTBuffer.Length)
throw new WPinternalsException("Bad GPT");
UInt32 PartitionOffset = TableOffset;
while (PartitionOffset < (TableOffset + TableSize))
{
string Name = ByteOperations.ReadUnicodeString(GPTBuffer, PartitionOffset + 0x38, 0x48).TrimEnd(new char[] { (char)0, ' ' });
if (Name.Length == 0)
break;
Partition CurrentPartition = new Partition();
CurrentPartition.Name = Name;
CurrentPartition.FirstSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x20);
CurrentPartition.LastSector = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x28);
CurrentPartition.PartitionTypeGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x00);
CurrentPartition.PartitionGuid = ByteOperations.ReadGuid(GPTBuffer, PartitionOffset + 0x10);
CurrentPartition.Attributes = ByteOperations.ReadUInt64(GPTBuffer, PartitionOffset + 0x30);
Partitions.Add(CurrentPartition);
PartitionOffset += PartitionEntrySize;
}
HasChanged = false;
}
internal Partition GetPartition(string Name)
{
return Partitions.Where(p => (string.Compare(p.Name, Name, true) == 0)).FirstOrDefault();
}
// Magic!
// SecureBoot hack for Bootloader Spec A starts here
internal byte[] InsertHack()
{
Partition HackPartition = Partitions.Where(p => (p.Name == "HACK")).FirstOrDefault();
Partition SBL1 = Partitions.Where(p => (p.Name == "SBL1")).FirstOrDefault();
Partition SBL2 = Partitions.Where(p => (p.Name == "SBL2")).FirstOrDefault();
if ((SBL1 == null) || (SBL2 == null))
throw new WPinternalsException("Bad GPT");
if (HackPartition == null)
{
HackPartition = new Partition();
HackPartition.Name = "HACK";
HackPartition.Attributes = SBL2.Attributes;
HackPartition.FirstSector = SBL1.LastSector;
HackPartition.LastSector = SBL1.LastSector;
HackPartition.PartitionTypeGuid = SBL2.PartitionTypeGuid;
HackPartition.PartitionGuid = SBL2.PartitionGuid;
Partitions.Add(HackPartition);
SBL1.LastSector--;
SBL2.PartitionTypeGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 });
SBL2.PartitionGuid = new Guid(new byte[] { 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74 });
}
HasChanged = true;
return Rebuild();
}
internal byte[] RemoveHack()
{
Partition HackPartition = Partitions.Where(p => (p.Name == "HACK")).FirstOrDefault();
Partition SBL1 = Partitions.Where(p => (p.Name == "SBL1")).FirstOrDefault();
Partition SBL2 = Partitions.Where(p => (p.Name == "SBL2")).FirstOrDefault();
if ((SBL1 == null) || (SBL2 == null))
throw new WPinternalsException("Bad GPT");
if (HackPartition != null)
{
SBL2.PartitionTypeGuid = HackPartition.PartitionTypeGuid;
SBL2.PartitionGuid = HackPartition.PartitionGuid;
Partitions.Remove(HackPartition);
SBL1.LastSector++;
}
HasChanged = true;
return Rebuild();
}
internal byte[] Rebuild()
{
if (GPTBuffer == null)
{
TableSize = 0x4200;
TableOffset = 0;
GPTBuffer = new byte[TableSize];
}
else
Array.Clear(GPTBuffer, (int)TableOffset, (int)TableSize);
UInt32 PartitionOffset = TableOffset;
foreach (Partition CurrentPartition in Partitions)
{
ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x00, CurrentPartition.PartitionTypeGuid);
ByteOperations.WriteGuid(GPTBuffer, PartitionOffset + 0x10, CurrentPartition.PartitionGuid);
ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x20, CurrentPartition.FirstSector);
ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x28, CurrentPartition.LastSector);
ByteOperations.WriteUInt64(GPTBuffer, PartitionOffset + 0x30, CurrentPartition.Attributes);
ByteOperations.WriteUnicodeString(GPTBuffer, PartitionOffset + 0x38, CurrentPartition.Name, 0x48);
PartitionOffset += PartitionEntrySize;
}
ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x58, ByteOperations.CRC32(GPTBuffer, TableOffset, TableSize));
ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, 0);
ByteOperations.WriteUInt32(GPTBuffer, HeaderOffset + 0x10, ByteOperations.CRC32(GPTBuffer, HeaderOffset, HeaderSize));
return GPTBuffer;
}
internal void MergePartitionsFromFile(string Path, bool RoundToChunks)
{
MergePartitions(File.ReadAllText(Path), RoundToChunks);
}
internal void MergePartitionsFromStream(Stream Partitions, bool RoundToChunks)
{
using (TextReader tr = new StreamReader(Partitions))
{
MergePartitions(tr.ReadToEnd(), RoundToChunks);
}
}
internal void MergePartitions(string Xml, bool RoundToChunks, ZipArchive Archive = null)
{
GPT GptToMerge;
if (Xml == null)
GptToMerge = new GPT();
else
{
XmlSerializer x = new XmlSerializer(typeof(GPT), "");
MemoryStream s = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(Xml));
GptToMerge = (GPT)x.Deserialize(s);
s.Dispose();
}
if (Archive != null)
{
foreach (Partition NewPartition in GptToMerge.Partitions)
{
ZipArchiveEntry Entry = Archive.Entries.Where(e => ((string.Compare(e.Name, NewPartition.Name, true) == 0) || (e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))))).FirstOrDefault();
if (Entry == null)
{
// There is a partition entry in the xml, but this partition is not present in the archive.
Partition OldPartition = GetPartition(NewPartition.Name);
if (OldPartition == null)
{
// The partition entry in the xml is also not present in the current partition table.
// It must have a know position and length.
if (NewPartition.LastSector == 0)
{
throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\"");
}
}
else
{
// The partition entry in the xml is also present in the current partition table.
// But since the partition is not present in the archive, the partition cannot be relocated.
// If the location of the new partition is specified, it must be the same as the current partition.
if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector))
throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\"");
if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector))
throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\"");
NewPartition.FirstSector = OldPartition.FirstSector;
NewPartition.LastSector = OldPartition.LastSector;
}
}
else
{
// The partition in the xml is also present in the archive.
// If the length is specified in the xml, it must match the file in the archive.
ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200;
using (DecompressedStream DecompressedStream = new DecompressedStream(Entry.Open()))
{
try
{
StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200;
}
catch { }
}
if (NewPartition.LastSector == 0)
{
NewPartition.SizeInSectors = StreamLengthInSectors;
}
else
{
if (NewPartition.SizeInSectors != StreamLengthInSectors)
throw new WPinternalsException("Inconsistent length specified for partition \"" + NewPartition.Name + "\"");
}
}
}
}
else
{
foreach (Partition NewPartition in GptToMerge.Partitions)
{
// This is a partition entry in the xml, and there is no archive.
Partition OldPartition = GetPartition(NewPartition.Name);
if (OldPartition == null)
{
// The partition entry in the xml is also not present in the current partition table.
// It must have a known position and length.
if (NewPartition.LastSector == 0)
{
throw new WPinternalsException("Unknown length for partition \"" + NewPartition.Name + "\"");
}
}
else
{
// The partition entry in the xml is also present in the current partition table.
// But since the partition is not present in the archive, the partition cannot be relocated.
// If the location of the new partition is specified, it must be the same as the current partition.
if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != OldPartition.FirstSector))
throw new WPinternalsException("Incorrect location for partition \"" + NewPartition.Name + "\"");
if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != OldPartition.LastSector))
throw new WPinternalsException("Incorrect length for partition \"" + NewPartition.Name + "\"");
NewPartition.FirstSector = OldPartition.FirstSector;
NewPartition.LastSector = OldPartition.LastSector;
}
}
}
List<Partition> DynamicPartitions = new List<Partition>();
if (Archive != null)
{
// Partitions which are present in the archive, and which have no start-sector in the new GPT data (dynamic relocation),
// and which can be clustered to the end of emmc, are first removed from the existing GPT.
IEnumerable<Partition> SortedPartitions = Partitions.OrderBy(p => p.FirstSector);
for (int i = SortedPartitions.Count() - 1; i >= 0; i--)
{
Partition OldPartition = SortedPartitions.ElementAt(i);
// Present in archive?
ZipArchiveEntry Entry = Archive.Entries.Where(e => ((string.Compare(e.Name, OldPartition.Name, true) == 0) || (e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))))).FirstOrDefault();
if (Entry != null)
{
// Not present in new GPT or present in GPT without FirstSector?
Partition NewPartition = GptToMerge.GetPartition(OldPartition.Name);
if ((NewPartition == null) || (NewPartition.FirstSector == 0))
{
DynamicPartitions.Insert(0, OldPartition);
this.Partitions.Remove(OldPartition);
}
else
break;
}
else
break;
}
}
// All partitions in the new GPT data should have a start-sector and end-sector by now.
// The partitions in the new GPT data will be applied to the current partition-table.
// Existing partitions, which are overwritten by the new partitions will be removed from the existing GPT.
// Existing partition with the same name in the existing GPT is reused (guids and attribs remain, if not specified).
UInt64 LowestSector = 0;
Partition DPP = this.GetPartition("DPP");
if (DPP != null)
LowestSector = DPP.LastSector + 1;
foreach (Partition NewPartition in GptToMerge.Partitions)
{
// If the new partition is a dynamic partition, then skip it here. It will be added later.
if (DynamicPartitions.Select(p => p.Name).Any(n => string.Compare(n, NewPartition.Name, true) == 0))
continue;
// Sanity check
if (NewPartition.FirstSector < LowestSector)
throw new WPinternalsException("Bad sector alignment for partition: " + NewPartition.Name);
Partition CurrentPartition = this.GetPartition(NewPartition.Name);
if (CurrentPartition == null)
{
CurrentPartition = new Partition();
CurrentPartition.Name = NewPartition.Name;
this.Partitions.Add(CurrentPartition);
HasChanged = true;
}
if ((NewPartition.FirstSector != 0) && (NewPartition.FirstSector != CurrentPartition.FirstSector))
{
CurrentPartition.FirstSector = NewPartition.FirstSector;
HasChanged = true;
}
if ((NewPartition.LastSector != 0) && (NewPartition.LastSector != CurrentPartition.LastSector))
{
CurrentPartition.LastSector = NewPartition.LastSector;
HasChanged = true;
}
if ((NewPartition.Attributes != 0) && (CurrentPartition.Attributes != NewPartition.Attributes))
{
CurrentPartition.Attributes = NewPartition.Attributes;
HasChanged = true;
}
if ((NewPartition.PartitionGuid == null) || (NewPartition.PartitionGuid != CurrentPartition.PartitionGuid))
HasChanged = true;
if (NewPartition.PartitionGuid != null)
CurrentPartition.PartitionGuid = NewPartition.PartitionGuid;
if (CurrentPartition.PartitionGuid == null)
CurrentPartition.PartitionGuid = Guid.NewGuid();
if ((NewPartition.PartitionTypeGuid == null) || (NewPartition.PartitionTypeGuid != CurrentPartition.PartitionTypeGuid))
HasChanged = true;
if (NewPartition.PartitionTypeGuid != null)
CurrentPartition.PartitionTypeGuid = NewPartition.PartitionTypeGuid;
if (CurrentPartition.PartitionTypeGuid == null)
CurrentPartition.PartitionTypeGuid = Guid.NewGuid();
for (int i = this.Partitions.Count - 1; i >= 0; i--)
{
if (this.Partitions[i] != CurrentPartition)
{
if ((CurrentPartition.FirstSector <= this.Partitions[i].LastSector) && (CurrentPartition.LastSector >= this.Partitions[i].FirstSector))
{
this.Partitions.RemoveAt(i);
HasChanged = true;
}
}
}
}
if (Archive != null)
{
// All partitions listed in the archive, which are present in the existing GPT, should overwrite the existing partition.
// Check if the sizes of the partitions in the archive do not exceed the size of the partition, as listed in the GPT.
foreach (Partition OldPartition in this.Partitions)
{
ZipArchiveEntry Entry = Archive.Entries.Where(e => ((string.Compare(e.Name, OldPartition.Name, true) == 0) || (e.Name.StartsWith(OldPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))))).FirstOrDefault();
if (Entry != null)
{
DecompressedStream DecompressedStream = new DecompressedStream(Entry.Open());
ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200;
try
{
StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200;
}
catch { }
DecompressedStream.Close();
UInt64 MaxPartitionSizeInSectors = OldPartition.SizeInSectors;
Partition NextPartition = this.Partitions.Where(p => p.FirstSector > OldPartition.FirstSector).OrderBy(p => p.FirstSector).FirstOrDefault();
if (NextPartition != null)
MaxPartitionSizeInSectors = NextPartition.FirstSector - OldPartition.FirstSector;
if (StreamLengthInSectors > MaxPartitionSizeInSectors)
{
throw new WPinternalsException("Incorrect length for partition \"" + OldPartition.Name + "\"");
}
if (OldPartition.SizeInSectors != StreamLengthInSectors)
{
OldPartition.SizeInSectors = StreamLengthInSectors;
HasChanged = true;
}
}
}
// All remaining partitions in the archive, which were listed in the original GPT,
// should be added at the end of the partition-table.
UInt64 FirstFreeSector = 0x5000;
if (this.Partitions.Count > 0)
{
FirstFreeSector = this.Partitions.Max(p => p.LastSector) + 1;
// Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash
if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0))
FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100);
}
foreach (Partition NewPartition in DynamicPartitions)
{
if (NewPartition.FirstSector != FirstFreeSector)
{
NewPartition.FirstSector = FirstFreeSector;
HasChanged = true;
}
ZipArchiveEntry Entry = Archive.Entries.Where(e => ((string.Compare(e.Name, NewPartition.Name, true) == 0) || (e.Name.StartsWith(NewPartition.Name + ".", true, System.Globalization.CultureInfo.GetCultureInfo("en-US"))))).FirstOrDefault();
DecompressedStream DecompressedStream = new DecompressedStream(Entry.Open());
ulong StreamLengthInSectors = (ulong)Entry.Length / 0x200;
try
{
StreamLengthInSectors = (ulong)DecompressedStream.Length / 0x200;
}
catch { }
DecompressedStream.Close();
if (NewPartition.SizeInSectors != StreamLengthInSectors)
{
NewPartition.SizeInSectors = StreamLengthInSectors;
HasChanged = true;
}
this.Partitions.Add(NewPartition);
FirstFreeSector += StreamLengthInSectors;
// Always start a new partition on a new chunk (0x100 sector boundary), to be more flexible during custom flash
if (RoundToChunks && (((double)FirstFreeSector % 0x100) != 0))
FirstFreeSector = (UInt64)(Math.Ceiling((double)FirstFreeSector / 0x100) * 0x100);
}
}
Rebuild();
}
internal void WritePartitions(string Path)
{
string DirPath = System.IO.Path.GetDirectoryName(Path);
if (!Directory.Exists(DirPath))
Directory.CreateDirectory(DirPath);
XmlSerializer x = new XmlSerializer(typeof(GPT), "");
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
System.IO.StreamWriter FileWriter = new System.IO.StreamWriter(Path);
x.Serialize(FileWriter, this, ns);
FileWriter.Close();
}
internal static GPT ReadPartitions(string Path)
{
XmlSerializer x = new XmlSerializer(typeof(GPT), "");
using (FileStream s = new FileStream(Path, FileMode.Open))
{
return (GPT)x.Deserialize(s);
}
}
internal void RestoreBackupPartitions()
{
// This is necessary, because the partitions and backup-partitions can exchange.
// This may cause the startsector to be higher than the maximum allowed sector for flashing with a Lumia V1 programmer (hardcoded in programmer)
List<string> RevisePartitions = new List<string>(new string[] { "SBL1", "SBL2", "SBL3", "UEFI", "TZ", "RPM", "WINSECAPP" });
foreach (string RevisePartitionName in RevisePartitions)
{
Partition RevisePartition = GetPartition(RevisePartitionName);
Partition ReviseBackupPartition = GetPartition("BACKUP_" + RevisePartitionName);
if ((RevisePartition != null) && (ReviseBackupPartition != null) && (RevisePartition.FirstSector > ReviseBackupPartition.FirstSector))
{
ulong OriginalFirstSector = RevisePartition.FirstSector;
ulong OriginalLastSector = RevisePartition.LastSector;
RevisePartition.FirstSector = ReviseBackupPartition.FirstSector;
RevisePartition.LastSector = ReviseBackupPartition.LastSector;
ReviseBackupPartition.FirstSector = OriginalFirstSector;
ReviseBackupPartition.LastSector = OriginalLastSector;
HasChanged = true;
}
if (RevisePartition.LastSector >= 0xF400)
throw new WPinternalsException("Unsupported partition layout!");
}
}
}
public class Partition
{
private UInt64 _SizeInSectors;
private UInt64 _FirstSector;
private UInt64 _LastSector;
public string Name; // 0x48
public Guid PartitionTypeGuid; // 0x10
public Guid PartitionGuid; // 0x10
[XmlIgnore]
internal UInt64 Attributes; // 0x08
[XmlIgnore]
internal UInt64 SizeInSectors
{
get
{
if (_SizeInSectors != 0)
return _SizeInSectors;
else
return LastSector - FirstSector + 1;
}
set
{
_SizeInSectors = value;
if (FirstSector != 0)
LastSector = FirstSector + _SizeInSectors - 1;
}
}
[XmlIgnore]
internal UInt64 FirstSector // 0x08
{
get
{
return _FirstSector;
}
set
{
_FirstSector = value;
if (_SizeInSectors != 0)
_LastSector = FirstSector + _SizeInSectors - 1;
}
}
[XmlIgnore]
internal UInt64 LastSector // 0x08
{
get
{
return _LastSector;
}
set
{
_LastSector = value;
_SizeInSectors = 0;
}
}
[XmlIgnore]
public string Volume
{
get
{
return @"\\?\Volume" + PartitionGuid.ToString("b") + @"\";
}
}
[XmlElement(ElementName = "FirstSector")]
public string FirstSectorAsString
{
get
{
return "0x" + FirstSector.ToString("X16");
}
set
{
FirstSector = Convert.ToUInt64(value, 16);
}
}
[XmlElement(ElementName = "LastSector")]
public string LastSectorAsString
{
get
{
return "0x" + LastSector.ToString("X16");
}
set
{
LastSector = Convert.ToUInt64(value, 16);
}
}
[XmlElement(ElementName = "Attributes")]
public string AttributesAsString
{
get
{
return "0x" + Attributes.ToString("X16");
}
set
{
Attributes = Convert.ToUInt64(value, 16);
}
}
}
}