diff --git a/Img2Ffu.Library/Writer/FlashPartFactory.cs b/Img2Ffu.Library/Writer/FlashPartFactory.cs new file mode 100644 index 0000000..044b1ab --- /dev/null +++ b/Img2Ffu.Library/Writer/FlashPartFactory.cs @@ -0,0 +1,206 @@ +using Img2Ffu.Writer.Streams; +using static Img2Ffu.GPT; + +namespace Img2Ffu.Writer +{ + internal static class FlashPartFactory + { + private static readonly string IS_UNLOCKED_PARTITION_NAME = "IS_UNLOCKED"; + private static readonly string HACK_PARTITION_NAME = "HACK"; + private static readonly string BACKUP_BS_NV_PARTITION_NAME = "BACKUP_BS_NV"; + private static readonly string UEFI_BS_NV_PARTITION_NAME = "UEFI_BS_NV"; + + private static (ulong StartOffset, ulong Length)[] GetBlockAlignedAllocationMap((ulong StartOffset, ulong Length)[] AllocationMap, ulong TotalSizeInBytes, ulong BlockSize) + { + List<(ulong StartOffset, ulong Length)> AllocatedBlocks = []; + + (ulong MaxStartOffset, ulong MaxLength) = AllocationMap.MaxBy(x => x.StartOffset + x.Length); + ulong MaxEndOffset = MaxStartOffset + MaxLength; + + ulong MaxNumberOfBlocks = (MaxEndOffset / BlockSize) + 1; + + (ulong MinStartOffset, ulong MinLength) = AllocationMap.MinBy(x => x.StartOffset); + ulong MinNumberOfBlocks = MinStartOffset / BlockSize; + + for (ulong CurrentBlockIndex = MinNumberOfBlocks; CurrentBlockIndex < MaxNumberOfBlocks; CurrentBlockIndex++) + { + ulong CurrentBlockStartOffset = CurrentBlockIndex * BlockSize; + ulong CurrentBlockEndOffset = CurrentBlockStartOffset + BlockSize; + + if (AllocationMap.Any(Allocation => CurrentBlockEndOffset > Allocation.StartOffset && Allocation.StartOffset + Allocation.Length > CurrentBlockStartOffset)) + { + if (AllocatedBlocks.Count == 0) + { + AllocatedBlocks.Add((CurrentBlockStartOffset, BlockSize)); + } + else + { + (ulong LastStartOffset, ulong LastLength) = AllocatedBlocks[^1]; + if (LastStartOffset + LastLength == CurrentBlockStartOffset) + { + AllocatedBlocks[^1] = (LastStartOffset, LastLength + BlockSize); + } + else + { + AllocatedBlocks.Add((CurrentBlockStartOffset, BlockSize)); + } + } + } + } + + ulong NewAllocationTotalSizeInBytes = AllocatedBlocks[^1].StartOffset + AllocatedBlocks[^1].Length; + + if (NewAllocationTotalSizeInBytes > TotalSizeInBytes) + { + AllocatedBlocks[^1] = (AllocatedBlocks[^1].StartOffset, AllocatedBlocks[^1].Length - (NewAllocationTotalSizeInBytes - TotalSizeInBytes)); + } + + return [.. AllocatedBlocks]; + } + + internal static List GetFlashParts(GPT GPT, Stream stream, uint BlockSize, string[] ExcludedPartitionNames, uint sectorSize, ILogging Logging) + { + uint sectorsInABlock = BlockSize / sectorSize; + + Logging.Log($"Sector Size: {sectorSize}"); + Logging.Log($"Block Size: {BlockSize}"); + Logging.Log($"Sectors in a Block: {sectorsInABlock}"); + + List Partitions = GPT.Partitions; + + bool isUnlocked = GPT.GetPartition(IS_UNLOCKED_PARTITION_NAME) != null; + bool isUnlockedSpecA = GPT.GetPartition(HACK_PARTITION_NAME) != null && GPT.GetPartition(BACKUP_BS_NV_PARTITION_NAME) != null; + + if (isUnlocked) + { + Logging.Log($"The phone is an unlocked Spec B phone, {UEFI_BS_NV_PARTITION_NAME} will be kept in the FFU image for the unlock to work"); + } + + if (isUnlockedSpecA) + { + Logging.Log($"The phone is an UEFI unlocked Spec A phone, {UEFI_BS_NV_PARTITION_NAME} will be kept in the FFU image for the unlock to work"); + } + + List flashParts = []; + + Logging.Log("Partitions with a * appended are ignored partitions"); + Logging.Log(""); + + int maxPartitionNameSize = Partitions.Select(x => x.Name.Length).Max() + 1; + int maxPartitionLastSector = Partitions.Select(x => x.LastSector.ToString().Length).Max() + 1; + + Logging.Log($"{"Name".PadRight(maxPartitionNameSize)} - " + + $"{"First".PadRight(maxPartitionLastSector)} - " + + $"{"Last".PadRight(maxPartitionLastSector)} - " + + $"{"Sectors".PadRight(maxPartitionLastSector)} - " + + $"{"Blocks".PadRight(maxPartitionLastSector)}", + ILoggingLevel.Information); + Logging.Log(""); + + ulong CurrentStartingOffset = 0; + ulong CurrentEndingOffset = 0; + List<(ulong StartOffset, ulong Length)> AllocatedPartitionsMap = []; + + foreach (Partition Partition in Partitions.OrderBy(x => x.FirstSector)) + { + bool IsPartitionExcluded = false; + + if (ExcludedPartitionNames.Any(x => x == Partition.Name)) + { + IsPartitionExcluded = true; + if (isUnlocked && Partition.Name == UEFI_BS_NV_PARTITION_NAME) + { + IsPartitionExcluded = false; + } + + if (isUnlockedSpecA && Partition.Name == UEFI_BS_NV_PARTITION_NAME) + { + IsPartitionExcluded = false; + } + } + + string name = $"{(IsPartitionExcluded ? "*" : "")}{Partition.Name}"; + + Logging.Log($"{name.PadRight(maxPartitionNameSize)} - " + + $"{(Partition.FirstSector + "s").PadRight(maxPartitionLastSector)} - " + + $"{(Partition.LastSector + "s").PadRight(maxPartitionLastSector)} - " + + $"{(Partition.SizeInSectors + "s").PadRight(maxPartitionLastSector)} - " + + $"{((Partition.SizeInSectors / (double)sectorsInABlock) + "c").PadRight(maxPartitionLastSector)}", + IsPartitionExcluded ? ILoggingLevel.Warning : ILoggingLevel.Information); + + ulong CurrentPartitionStartingOffset = Partition.FirstSector * sectorSize; + ulong CurrentPartitionEndingOffset = (Partition.LastSector + 1) * sectorSize; + + if (IsPartitionExcluded) + { + if (AllocatedPartitionsMap.Count != 0) + { + ulong AllocationTotalSizeInBytes = CurrentEndingOffset - CurrentStartingOffset; + (ulong StartOffset, ulong Length)[] AllocatedBlocks = GetBlockAlignedAllocationMap([.. AllocatedPartitionsMap], AllocationTotalSizeInBytes, BlockSize); + + foreach ((ulong StartOffset, ulong Length) in AllocatedBlocks) + { + ulong blockStartOffset = CurrentStartingOffset + StartOffset; + PartialStream blockStream = new(stream, (long)blockStartOffset, (long)(blockStartOffset + Length)); + FlashPart flashPart = new(blockStream, blockStartOffset); + flashParts.Add(flashPart); + } + + AllocatedPartitionsMap.Clear(); + CurrentStartingOffset = 0; + CurrentEndingOffset = 0; + } + } + else + { + // 0 would be the GPT, we can't land in this case here because we deal with partitions + if (CurrentStartingOffset == 0) + { + CurrentStartingOffset = CurrentPartitionStartingOffset; + CurrentEndingOffset = CurrentPartitionEndingOffset; + } + else + { + CurrentEndingOffset = CurrentPartitionEndingOffset; + } + + ulong allocationOffset = CurrentPartitionStartingOffset - CurrentStartingOffset; + + PartialStream partialStream = new(stream, (long)CurrentPartitionStartingOffset, (long)CurrentPartitionEndingOffset); + + /*if (FileSystemAllocationUtils.IsNTFS(partialStream)) + { + (ulong StartOffset, ulong Length)[] AllocatedClusterMap = FileSystemAllocationUtils.GetNTFSAllocatedClustersMap(partialStream); + //AllocatedPartitionsMap.AddRange(AllocatedClusterMap.Select(x => (allocationOffset + x.StartOffset, x.Length))); + + (ulong StartOffset, ulong Length) lastElement = AllocatedClusterMap.MaxBy(x => x.StartOffset); + AllocatedPartitionsMap.Add((allocationOffset, lastElement.StartOffset + lastElement.Length)); + } + else*/ + { + AllocatedPartitionsMap.Add((allocationOffset, Partition.SizeInSectors * sectorSize)); + } + } + } + + if (AllocatedPartitionsMap.Count != 0) + { + (ulong StartOffset, ulong Length)[] AllocatedBlocks = GetBlockAlignedAllocationMap([.. AllocatedPartitionsMap], CurrentStartingOffset - CurrentEndingOffset, BlockSize); + + foreach ((ulong StartOffset, ulong Length) in AllocatedBlocks) + { + ulong blockStartOffset = CurrentStartingOffset + StartOffset; + PartialStream blockStream = new(stream, (long)blockStartOffset, (long)(blockStartOffset + Length)); + FlashPart flashPart = new(blockStream, blockStartOffset); + flashParts.Add(flashPart); + } + + AllocatedPartitionsMap.Clear(); + CurrentStartingOffset = 0; + CurrentEndingOffset = 0; + } + + return flashParts; + } + } +} diff --git a/Img2Ffu.Library/Writer/ImageSplitter.cs b/Img2Ffu.Library/Writer/ImageSplitter.cs index 0dfdf6b..f137fa8 100644 --- a/Img2Ffu.Library/Writer/ImageSplitter.cs +++ b/Img2Ffu.Library/Writer/ImageSplitter.cs @@ -21,19 +21,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -using Img2Ffu.Writer.Streams; using static Img2Ffu.GPT; namespace Img2Ffu.Writer { - internal class ImageSplitter + internal static class ImageSplitter { - internal static readonly string IS_UNLOCKED_PARTITION_NAME = "IS_UNLOCKED"; - internal static readonly string HACK_PARTITION_NAME = "HACK"; - internal static readonly string BACKUP_BS_NV_PARTITION_NAME = "BACKUP_BS_NV"; - internal static readonly string UEFI_BS_NV_PARTITION_NAME = "UEFI_BS_NV"; - - internal static GPT GetGPT(Stream stream, uint BlockSize, uint sectorSize, ILogging Logging) + private static GPT GetGPT(Stream stream, uint BlockSize, uint sectorSize, ILogging Logging) { byte[] GPTBuffer = new byte[BlockSize]; _ = stream.Read(GPTBuffer, 0, (int)BlockSize); @@ -67,143 +61,7 @@ internal static (FlashPart[], List partitions) GetImageSlices(Stream GPT GPT = GetGPT(stream, BlockSize, sectorSize, Logging); uint sectorsInABlock = BlockSize / sectorSize; - Logging.Log($"Sector Size: {sectorSize}"); - Logging.Log($"Block Size: {BlockSize}"); - Logging.Log($"Sectors in a Block: {sectorsInABlock}"); - - List Partitions = GPT.Partitions; - - bool isUnlocked = GPT.GetPartition(IS_UNLOCKED_PARTITION_NAME) != null; - bool isUnlockedSpecA = GPT.GetPartition(HACK_PARTITION_NAME) != null && GPT.GetPartition(BACKUP_BS_NV_PARTITION_NAME) != null; - - if (isUnlocked) - { - Logging.Log($"The phone is an unlocked Spec B phone, {UEFI_BS_NV_PARTITION_NAME} will be kept in the FFU image for the unlock to work"); - } - - if (isUnlockedSpecA) - { - Logging.Log($"The phone is an UEFI unlocked Spec A phone, {UEFI_BS_NV_PARTITION_NAME} will be kept in the FFU image for the unlock to work"); - } - - List flashParts = []; - - Logging.Log("Partitions with a * appended are ignored partitions"); - Logging.Log(""); - - int maxPartitionNameSize = Partitions.Select(x => x.Name.Length).Max() + 1; - int maxPartitionLastSector = Partitions.Select(x => x.LastSector.ToString().Length).Max() + 1; - - Logging.Log($"{"Name".PadRight(maxPartitionNameSize)} - " + - $"{"First".PadRight(maxPartitionLastSector)} - " + - $"{"Last".PadRight(maxPartitionLastSector)} - " + - $"{"Sectors".PadRight(maxPartitionLastSector)} - " + - $"{"Blocks".PadRight(maxPartitionLastSector)}", - ILoggingLevel.Information); - Logging.Log(""); - - ulong CurrentStartingOffset = 0; - ulong CurrentEndingOffset = 0; - List<(ulong StartOffset, ulong Length)> AllocatedPartitionsMap = []; - - foreach (Partition Partition in Partitions.OrderBy(x => x.FirstSector)) - { - bool IsPartitionExcluded = false; - - if (ExcludedPartitionNames.Any(x => x == Partition.Name)) - { - IsPartitionExcluded = true; - if (isUnlocked && Partition.Name == UEFI_BS_NV_PARTITION_NAME) - { - IsPartitionExcluded = false; - } - - if (isUnlockedSpecA && Partition.Name == UEFI_BS_NV_PARTITION_NAME) - { - IsPartitionExcluded = false; - } - } - - string name = $"{(IsPartitionExcluded ? "*" : "")}{Partition.Name}"; - - Logging.Log($"{name.PadRight(maxPartitionNameSize)} - " + - $"{(Partition.FirstSector + "s").PadRight(maxPartitionLastSector)} - " + - $"{(Partition.LastSector + "s").PadRight(maxPartitionLastSector)} - " + - $"{(Partition.SizeInSectors + "s").PadRight(maxPartitionLastSector)} - " + - $"{((Partition.SizeInSectors / (double)sectorsInABlock) + "c").PadRight(maxPartitionLastSector)}", - IsPartitionExcluded ? ILoggingLevel.Warning : ILoggingLevel.Information); - - ulong CurrentPartitionStartingOffset = Partition.FirstSector * sectorSize; - ulong CurrentPartitionEndingOffset = (Partition.LastSector + 1) * sectorSize; - - if (IsPartitionExcluded) - { - if (AllocatedPartitionsMap.Count != 0) - { - ulong AllocationTotalSizeInBytes = CurrentEndingOffset - CurrentStartingOffset; - (ulong StartOffset, ulong Length)[] AllocatedBlocks = GetBlockAlignedAllocationMap([.. AllocatedPartitionsMap], AllocationTotalSizeInBytes, BlockSize); - - foreach ((ulong StartOffset, ulong Length) in AllocatedBlocks) - { - ulong blockStartOffset = CurrentStartingOffset + StartOffset; - PartialStream blockStream = new(stream, (long)blockStartOffset, (long)(blockStartOffset + Length)); - FlashPart flashPart = new(blockStream, blockStartOffset); - flashParts.Add(flashPart); - } - - AllocatedPartitionsMap.Clear(); - CurrentStartingOffset = 0; - CurrentEndingOffset = 0; - } - } - else - { - // 0 would be the GPT, we can't land in this case here because we deal with partitions - if (CurrentStartingOffset == 0) - { - CurrentStartingOffset = CurrentPartitionStartingOffset; - CurrentEndingOffset = CurrentPartitionEndingOffset; - } - else - { - CurrentEndingOffset = CurrentPartitionEndingOffset; - } - - ulong allocationOffset = CurrentPartitionStartingOffset - CurrentStartingOffset; - - PartialStream partialStream = new(stream, (long)CurrentPartitionStartingOffset, (long)CurrentPartitionEndingOffset); - - /*if (FileSystemAllocationUtils.IsNTFS(partialStream)) - { - (ulong StartOffset, ulong Length)[] AllocatedClusterMap = FileSystemAllocationUtils.GetNTFSAllocatedClustersMap(partialStream); - //AllocatedPartitionsMap.AddRange(AllocatedClusterMap.Select(x => (allocationOffset + x.StartOffset, x.Length))); - - (ulong StartOffset, ulong Length) lastElement = AllocatedClusterMap.MaxBy(x => x.StartOffset); - AllocatedPartitionsMap.Add((allocationOffset, lastElement.StartOffset + lastElement.Length)); - } - else*/ - { - AllocatedPartitionsMap.Add((allocationOffset, Partition.SizeInSectors * sectorSize)); - } - } - } - - if (AllocatedPartitionsMap.Count != 0) - { - (ulong StartOffset, ulong Length)[] AllocatedBlocks = GetBlockAlignedAllocationMap([.. AllocatedPartitionsMap], CurrentStartingOffset - CurrentEndingOffset, BlockSize); - - foreach ((ulong StartOffset, ulong Length) in AllocatedBlocks) - { - ulong blockStartOffset = CurrentStartingOffset + StartOffset; - PartialStream blockStream = new(stream, (long)blockStartOffset, (long)(blockStartOffset + Length)); - FlashPart flashPart = new(blockStream, blockStartOffset); - flashParts.Add(flashPart); - } - - AllocatedPartitionsMap.Clear(); - CurrentStartingOffset = 0; - CurrentEndingOffset = 0; - } + List flashParts = FlashPartFactory.GetFlashParts(GPT, stream, BlockSize, ExcludedPartitionNames, sectorSize, Logging); FlashPart[] finalFlashParts = [.. flashParts]; @@ -234,58 +92,10 @@ internal static (FlashPart[], List partitions) GetImageSlices(Stream } } - return (finalFlashParts, Partitions); - } - - internal static (ulong StartOffset, ulong Length)[] GetBlockAlignedAllocationMap((ulong StartOffset, ulong Length)[] AllocationMap, ulong TotalSizeInBytes, ulong BlockSize) - { - List<(ulong StartOffset, ulong Length)> AllocatedBlocks = []; - - (ulong MaxStartOffset, ulong MaxLength) = AllocationMap.MaxBy(x => x.StartOffset + x.Length); - ulong MaxEndOffset = MaxStartOffset + MaxLength; - - ulong MaxNumberOfBlocks = (MaxEndOffset / BlockSize) + 1; - - (ulong MinStartOffset, ulong MinLength) = AllocationMap.MinBy(x => x.StartOffset); - ulong MinNumberOfBlocks = MinStartOffset / BlockSize; - - for (ulong CurrentBlockIndex = MinNumberOfBlocks; CurrentBlockIndex < MaxNumberOfBlocks; CurrentBlockIndex++) - { - ulong CurrentBlockStartOffset = CurrentBlockIndex * BlockSize; - ulong CurrentBlockEndOffset = CurrentBlockStartOffset + BlockSize; - - if (AllocationMap.Any(Allocation => CurrentBlockEndOffset > Allocation.StartOffset && Allocation.StartOffset + Allocation.Length > CurrentBlockStartOffset)) - { - if (AllocatedBlocks.Count == 0) - { - AllocatedBlocks.Add((CurrentBlockStartOffset, BlockSize)); - } - else - { - (ulong LastStartOffset, ulong LastLength) = AllocatedBlocks[^1]; - if (LastStartOffset + LastLength == CurrentBlockStartOffset) - { - AllocatedBlocks[^1] = (LastStartOffset, LastLength + BlockSize); - } - else - { - AllocatedBlocks.Add((CurrentBlockStartOffset, BlockSize)); - } - } - } - } - - ulong NewAllocationTotalSizeInBytes = AllocatedBlocks[^1].StartOffset + AllocatedBlocks[^1].Length; - - if (NewAllocationTotalSizeInBytes > TotalSizeInBytes) - { - AllocatedBlocks[^1] = (AllocatedBlocks[^1].StartOffset, AllocatedBlocks[^1].Length - (NewAllocationTotalSizeInBytes - TotalSizeInBytes)); - } - - return [.. AllocatedBlocks]; + return (finalFlashParts, GPT.Partitions); } - internal static void PrintFlashParts(FlashPart[] finalFlashParts, uint sectorSize, uint BlockSize, ILogging Logging) + private static void PrintFlashParts(FlashPart[] finalFlashParts, uint sectorSize, uint BlockSize, ILogging Logging) { for (int i = 0; i < finalFlashParts.Length; i++) { @@ -294,7 +104,7 @@ internal static void PrintFlashParts(FlashPart[] finalFlashParts, uint sectorSiz } } - internal static void PrintFlashPart(FlashPart flashPart, uint sectorSize, uint BlockSize, string name, ILogging Logging) + private static void PrintFlashPart(FlashPart flashPart, uint sectorSize, uint BlockSize, string name, ILogging Logging) { uint sectorsInABlock = BlockSize / sectorSize;