diff --git a/Reader/Compression/WindowsNativeCompression.cs b/Reader/Compression/WindowsNativeCompression.cs new file mode 100644 index 0000000..3cdff71 --- /dev/null +++ b/Reader/Compression/WindowsNativeCompression.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Compression +{ + internal class WindowsNativeCompression + { + [DllImport("ntdll.dll")] + private static extern uint RtlGetCompressionWorkSpaceSize(ushort CompressionFormatAndEngine, out uint CompressBufferWorkSpaceSize, out uint CompressFragmentWorkSpaceSize); + + [DllImport("ntdll.dll")] + private static extern uint RtlDecompressBufferEx(ushort CompressionFormat, byte[] UncompressedBuffer, int UncompressedBufferSize, byte[] CompressedBuffer, int CompressedBufferSize, out int FinalUncompressedSize, byte[] WorkSpace); + + [DllImport("ntdll.dll")] + private static extern uint RtlCompressBuffer(ushort CompressionFormatAndEngine, byte[] UncompressedBuffer, int UncompressedBufferSize, byte[] CompressedBuffer, int CompressedBufferSize, int UncompressedChunkSize, out int FinalCompressedSize, byte[] WorkSpace); + + public static byte[] Decompress(WindowsNativeCompressionAlgorithm CompressionFormat, byte[] CompressedBuffer, int UncompressedBufferSize) + { + uint ntStatus = RtlGetCompressionWorkSpaceSize((ushort)CompressionFormat, out uint compressBufferWorkSpaceSize, out _); + if (ntStatus != 0) + { + throw new Exception($"Cannot get decompress workspace size! NTSTATUS=0x{ntStatus:X8}"); + } + + byte[] UncompressedBuffer = new byte[UncompressedBufferSize]; + byte[] workSpace = new byte[compressBufferWorkSpaceSize]; + + + ntStatus = RtlDecompressBufferEx((ushort)CompressionFormat, UncompressedBuffer, UncompressedBuffer.Length, CompressedBuffer, CompressedBuffer.Length, out _, workSpace); + return ntStatus != 0 ? throw new Exception($"Cannot decompress buffer! NTSTATUS=0x{ntStatus:X8}") : UncompressedBuffer; + } + + public static byte[] Compress(WindowsNativeCompressionAlgorithm CompressionFormat, byte[] UncompressedBuffer, int CompressedBufferSize) + { + uint ntStatus = RtlGetCompressionWorkSpaceSize((ushort)CompressionFormat, out uint compressBufferWorkSpaceSize, out _); + if (ntStatus != 0) + { + throw new Exception($"Cannot get compression workspace size! NTSTATUS=0x{ntStatus:X8}"); + } + + byte[] CompressedBuffer = new byte[CompressedBufferSize]; + byte[] workSpace = new byte[compressBufferWorkSpaceSize]; + + + ntStatus = RtlCompressBuffer((ushort)CompressionFormat, UncompressedBuffer, UncompressedBuffer.Length, CompressedBuffer, CompressedBuffer.Length, 4096, out _, workSpace); + return ntStatus != 0 ? throw new Exception($"Cannot compress buffer! NTSTATUS=0x{ntStatus:X8}") : CompressedBuffer; + } + } +} diff --git a/Reader/Compression/WindowsNativeCompressionAlgorithm.cs b/Reader/Compression/WindowsNativeCompressionAlgorithm.cs new file mode 100644 index 0000000..3d5ff20 --- /dev/null +++ b/Reader/Compression/WindowsNativeCompressionAlgorithm.cs @@ -0,0 +1,17 @@ +using System; + +namespace Img2Ffu.Reader.Compression +{ + [Flags] + public enum WindowsNativeCompressionAlgorithm + { + COMPRESSION_FORMAT_NONE = 0x0000, + COMPRESSION_ENGINE_STANDARD = 0x0000, + COMPRESSION_FORMAT_DEFAULT = 0x0001, + COMPRESSION_FORMAT_LZNT1 = 0x0002, + COMPRESSION_FORMAT_XPRESS = 0x0003, + COMPRESSION_FORMAT_XPRESS_HUFF = 0x0004, + COMPRESSION_ENGINE_MAXIMUM = 0x0100, + COMPRESSION_ENGINE_HIBER = 0x0200 + } +} diff --git a/Reader/Data/ImageFlash.cs b/Reader/Data/ImageFlash.cs new file mode 100644 index 0000000..89517e9 --- /dev/null +++ b/Reader/Data/ImageFlash.cs @@ -0,0 +1,202 @@ +using Img2Ffu.Reader.Compression; +using Img2Ffu.Reader.Enums; +using Img2Ffu.Reader.Structs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Img2Ffu.Reader.Data +{ + internal class ImageFlash + { + public ImageHeader ImageHeader; + public string ManifestData = ""; + public byte[] Padding = []; + public readonly List Stores = []; + + private readonly long DataBlocksPosition; + private readonly long InitialStreamPosition; + + + public ImageFlash(Stream stream) + { + InitialStreamPosition = stream.Position; + ImageHeader = stream.ReadStructure(); + + if (ImageHeader.Signature != "ImageFlash ") + { + throw new InvalidDataException("Invalid Image Header Signature!"); + } + + byte[] manifestDataBuffer = new byte[ImageHeader.ManifestLength]; + _ = stream.Read(manifestDataBuffer, 0, (int)ImageHeader.ManifestLength); + ASCIIEncoding asciiEncoding = new(); + ManifestData = asciiEncoding.GetString(manifestDataBuffer); + + long Position = stream.Position; + if (Position % (ImageHeader.ChunkSize * 1024) > 0) + { + long paddingSize = (ImageHeader.ChunkSize * 1024) - (Position % (ImageHeader.ChunkSize * 1024)); + Padding = new byte[paddingSize]; + _ = stream.Read(Padding, 0, (int)paddingSize); + } + + Store store = new(stream); + Stores.Add(store); + + if (store.StoreHeader.MajorVersion == 2 && store.StoreHeader.FullFlashMajorVersion == 2) + { + for (uint i = 2; i <= store.StoreHeaderV2.NumberOfStores; i++) + { + Stores.Add(new Store(stream)); + } + } + + DataBlocksPosition = stream.Position; + } + + + public ulong GetImageBlockCount() + { + ulong imageBlockCount = (ulong)(DataBlocksPosition - InitialStreamPosition) / (ImageHeader.ChunkSize * 1024); + ulong storeBlockCount = GetDataBlockCount(); + return imageBlockCount + storeBlockCount; + } + + public byte[] GetImageBlock(Stream Stream, ulong dataBlockIndex) + { + ulong imageBlockCount = (ulong)(DataBlocksPosition - InitialStreamPosition) / (ImageHeader.ChunkSize * 1024); + + // The data block is within the image headers + if (dataBlockIndex < imageBlockCount) + { + byte[] dataBlock = new byte[(ImageHeader.ChunkSize * 1024)]; + ulong dataBlockPosition = (ulong)InitialStreamPosition + (dataBlockIndex * (ImageHeader.ChunkSize * 1024)); + + _ = Stream.Seek((long)dataBlockPosition, SeekOrigin.Begin); + _ = Stream.Read(dataBlock, 0, dataBlock.Length); + + return dataBlock; + } + // The data block is within the store data blocks, those may be compressed + else + { + return GetDataBlock(Stream, dataBlockIndex - imageBlockCount); + } + } + + + public ulong GetDataBlockCount() + { + ulong dataBlockCount = 0; + foreach (Store store in Stores) + { + dataBlockCount += (ulong)store.WriteDescriptors.LongCount(); + } + return dataBlockCount; + } + + public byte[] GetDataBlock(Stream Stream, ulong dataBlockIndex) + { + ulong dataBlockIndexOffset = 0; + for (ulong storeIndex = 0; storeIndex < (ulong)Stores.Count; storeIndex++) + { + ulong storeDataBlockCount = GetStoreDataBlockCount(storeIndex); + if (dataBlockIndexOffset + storeDataBlockCount > dataBlockIndex) + { + return GetStoreDataBlock(Stream, storeIndex, dataBlockIndex - dataBlockIndexOffset); + } + + dataBlockIndexOffset += storeDataBlockCount; + } + + throw new Exception("Invalid data block index value"); + } + + + public ulong GetStoreDataBlockCount(ulong storeIndex) + { + return (ulong)Stores[(int)storeIndex].WriteDescriptors.LongCount(); + } + + public byte[] GetStoreDataBlock(Stream Stream, ulong storeIndex, ulong dataBlockIndex) + { + ulong dataBlockPosition = (ulong)DataBlocksPosition; + for (ulong s = 0; s < storeIndex; s++) + { + Store store = Stores[(int)s]; + if (store.CompressionAlgorithm != 0) + { + foreach (WriteDescriptor writeDescriptor in store.WriteDescriptors) + { + dataBlockPosition += writeDescriptor.DataSize; + } + } + else + { + dataBlockPosition += store.StoreHeader.WriteDescriptorCount * store.StoreHeader.BlockSize; + } + } + + Store currentStore = Stores[(int)storeIndex]; + byte[] dataBlock = new byte[currentStore.StoreHeader.BlockSize]; + + CompressionAlgorithm compressionAlgorithm = (CompressionAlgorithm)currentStore.CompressionAlgorithm; + + if (compressionAlgorithm != CompressionAlgorithm.None) + { + for (ulong i = 0; i < dataBlockIndex; i++) + { + WriteDescriptor writeDescriptor = currentStore.WriteDescriptors[(int)i]; + dataBlockPosition += writeDescriptor.DataSize; + } + + WriteDescriptor currentWriteDescriptor = currentStore.WriteDescriptors[(int)dataBlockIndex]; + uint compressedDataBlockSize = currentWriteDescriptor.DataSize; + byte[] compressedDataBlock = new byte[compressedDataBlockSize]; + + _ = Stream.Seek((long)dataBlockPosition, SeekOrigin.Begin); + _ = Stream.Read(compressedDataBlock, 0, compressedDataBlock.Length); + + switch ((CompressionAlgorithm)currentStore.CompressionAlgorithm) + { + case CompressionAlgorithm.Default: + { + dataBlock = WindowsNativeCompression.Decompress(WindowsNativeCompressionAlgorithm.COMPRESSION_FORMAT_XPRESS, compressedDataBlock, (int)currentStore.StoreHeader.BlockSize); + break; + } + case CompressionAlgorithm.LZNT1: + { + dataBlock = WindowsNativeCompression.Decompress(WindowsNativeCompressionAlgorithm.COMPRESSION_FORMAT_LZNT1, compressedDataBlock, (int)currentStore.StoreHeader.BlockSize); + break; + } + case CompressionAlgorithm.XPRESS: + { + dataBlock = WindowsNativeCompression.Decompress(WindowsNativeCompressionAlgorithm.COMPRESSION_FORMAT_XPRESS, compressedDataBlock, (int)currentStore.StoreHeader.BlockSize); + break; + } + case CompressionAlgorithm.XPRESS_HUFF: + { + dataBlock = WindowsNativeCompression.Decompress(WindowsNativeCompressionAlgorithm.COMPRESSION_FORMAT_XPRESS_HUFF, compressedDataBlock, (int)currentStore.StoreHeader.BlockSize); + break; + } + default: + { + throw new NotImplementedException("The compression algorithm this data block uses is not currently implemented."); + } + } + } + else + { + dataBlockPosition += dataBlockIndex * currentStore.StoreHeader.BlockSize; + + _ = Stream.Seek((long)dataBlockPosition, SeekOrigin.Begin); + _ = Stream.Read(dataBlock, 0, dataBlock.Length); + } + + return dataBlock; + } + } +} diff --git a/Reader/Data/SignedImage.cs b/Reader/Data/SignedImage.cs new file mode 100644 index 0000000..5a7a88a --- /dev/null +++ b/Reader/Data/SignedImage.cs @@ -0,0 +1,132 @@ +using Img2Ffu.Reader.Structs; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Img2Ffu.Reader.Data +{ + internal class SignedImage + { + public SecurityHeader SecurityHeader; + public byte[] Catalog; + public byte[] TableOfHashes; + public byte[] Padding = []; + public ImageFlash Image; + + public ulong HeaderSize { get; } + public int ChunkSize => (int)(SecurityHeader.ChunkSizeInKB * 1024); + public ulong TotalChunkCount => Image.GetImageBlockCount(); + + public readonly List BlockHashes = []; + public X509Certificate Certificate; + + private readonly Stream Stream; + + public SignedImage(Stream stream) + { + Stream = stream; + + SecurityHeader = stream.ReadStructure(); + + if (SecurityHeader.Signature != "SignedImage ") + { + throw new InvalidDataException("Invalid Security Header Signature!"); + } + + Catalog = new byte[SecurityHeader.CatalogSize]; + _ = stream.Read(Catalog, 0, (int)SecurityHeader.CatalogSize); + + TableOfHashes = new byte[SecurityHeader.HashTableSize]; + _ = stream.Read(TableOfHashes, 0, (int)SecurityHeader.HashTableSize); + + long Position = stream.Position; + uint sizeOfBlock = SecurityHeader.ChunkSizeInKB * 1024; + if (Position % sizeOfBlock > 0) + { + long paddingSize = sizeOfBlock - (Position % sizeOfBlock); + Padding = new byte[paddingSize]; + _ = stream.Read(Padding, 0, (int)paddingSize); + } + + try + { + Certificate = new X509Certificate(Catalog); + } + catch { } + + Image = new ImageFlash(stream); + + HeaderSize = (ulong)stream.Position; + + ParseBlockHashes(); + } + + private void ParseBlockHashes() + { + BlockHashes.Clear(); + + ulong numberOfBlocksToVerify = Image.GetImageBlockCount(); + ulong sizeOfHash = (ulong)TableOfHashes.LongLength / numberOfBlocksToVerify; + + for (int i = 0; i < TableOfHashes.Length; i += (int)sizeOfHash) + { + BlockHashes.Add(TableOfHashes[i..(i + (int)sizeOfHash)]); + } + } + + private uint GetImageHeaderPosition() + { + uint sizeOfBlock = SecurityHeader.ChunkSizeInKB * 1024; + uint ImageHeaderPosition = SecurityHeader.Size + SecurityHeader.CatalogSize + SecurityHeader.HashTableSize; + if (ImageHeaderPosition % sizeOfBlock > 0) + { + uint paddingSize = sizeOfBlock - (ImageHeaderPosition % sizeOfBlock); + ImageHeaderPosition += paddingSize; + } + return ImageHeaderPosition; + } + + public void VerifyFFU() + { + ulong numberOfBlocksToVerify = Image.GetImageBlockCount(); + + long oldPosition = Stream.Position; + + using BinaryReader binaryReader = new(Stream, Encoding.ASCII, true); + using BinaryReader hashTableBinaryReader = new(new MemoryStream(TableOfHashes)); + + for (ulong i = 0; i < numberOfBlocksToVerify; i++) + { + Console.Title = $"{i + 1}/{numberOfBlocksToVerify}"; + + if (SecurityHeader.AlgorithmId == 0x0000800c) // SHA256 Algorithm ID + { + byte[] block = Image.GetImageBlock(Stream, i); + byte[] hash = SHA256.HashData(block); + byte[] hashTableHash = BlockHashes.ElementAt((int)i); + + if (!StructuralComparisons.StructuralEqualityComparer.Equals(hash, hashTableHash)) + { + _ = Stream.Seek(oldPosition, SeekOrigin.Begin); + throw new InvalidDataException($"The FFU image contains a mismatched hash value at chunk {i}. " + + $"Expected: {BitConverter.ToString(hashTableHash).Replace("-", "")} " + + $"Computed: {BitConverter.ToString(hash).Replace("-", "")}"); + } + } + else + { + throw new InvalidDataException($"Unknown Hash algorithm id: {SecurityHeader.AlgorithmId}"); + } + } + + Console.Title = $"{numberOfBlocksToVerify}/{numberOfBlocksToVerify}"; + + _ = Stream.Seek(oldPosition, SeekOrigin.Begin); + } + } +} \ No newline at end of file diff --git a/Reader/Data/Store.cs b/Reader/Data/Store.cs new file mode 100644 index 0000000..9d6a041 --- /dev/null +++ b/Reader/Data/Store.cs @@ -0,0 +1,95 @@ +using Img2Ffu.Reader.Enums; +using Img2Ffu.Reader.Structs; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Img2Ffu.Reader.Data +{ + internal class Store + { + public StoreHeader StoreHeader; + public uint CompressionAlgorithm; + public StoreHeaderV2 StoreHeaderV2; + public string DevicePath = ""; // Device path has no NUL at then end (V2 only) + public readonly List ValidationDescriptor = []; + public readonly List WriteDescriptors = []; + public readonly List PlatformIDs = []; + public byte[] Padding = []; + public readonly FFUVersion FFUVersion; + + public Store(Stream stream) + { + StoreHeader = stream.ReadStructure(); + + int lastFinding = 0; + for (int i = 0; i < StoreHeader.PlatformId.Length; i++) + { + if (StoreHeader.PlatformId[i] == '\0') + { + if (i == lastFinding) + { + break; + } + + ASCIIEncoding asciiEnconding = new(); + string PlatformID = asciiEnconding.GetString(StoreHeader.PlatformId[lastFinding..i]); + PlatformIDs.Add(PlatformID); + lastFinding = i + 1; + } + } + + FFUVersion = GetFFUVersion(); + + switch (FFUVersion) + { + case FFUVersion.V2: + { + StoreHeaderV2 = stream.ReadStructure(); + byte[] stringBytes = new byte[StoreHeaderV2.DevicePathLength * 2]; + _ = stream.Read(stringBytes, 0, stringBytes.Length); + UnicodeEncoding unicodeEncoding = new(); + DevicePath = unicodeEncoding.GetString(stringBytes); + break; + } + case FFUVersion.V1_COMPRESSED: + { + using BinaryReader binaryReader = new(stream, Encoding.ASCII, true); + CompressionAlgorithm = binaryReader.ReadUInt32(); + break; + } + } + + for (uint i = 0; i < StoreHeader.ValidateDescriptorCount; i++) + { + ValidationDescriptor.Add(new ValidationDescriptor(stream)); + } + + for (uint i = 0; i < StoreHeader.WriteDescriptorCount; i++) + { + WriteDescriptors.Add(new WriteDescriptor(stream, FFUVersion)); + } + + long Position = stream.Position; + if (Position % StoreHeader.BlockSize > 0) + { + long paddingSize = StoreHeader.BlockSize - (Position % StoreHeader.BlockSize); + Padding = new byte[paddingSize]; + _ = stream.Read(Padding, 0, (int)paddingSize); + } + } + + public override string ToString() + { + return $"{{StoreHeader: {StoreHeader}, StoreHeaderV2: {StoreHeaderV2}, DevicePath: {DevicePath}}}"; + } + + private FFUVersion GetFFUVersion() => (StoreHeader.MajorVersion, StoreHeader.FullFlashMajorVersion) switch + { + (1, 2) => FFUVersion.V1, + (1, 3) => FFUVersion.V1_COMPRESSED, + (2, 2) => FFUVersion.V2, + _ => throw new InvalidDataException($"Unsupported FFU Store Format! MajorVersion: {StoreHeader.MajorVersion} FullFlashMajorVersion: {StoreHeader.FullFlashMajorVersion}"), + }; + } +} diff --git a/Reader/Data/ValidationDescriptor.cs b/Reader/Data/ValidationDescriptor.cs new file mode 100644 index 0000000..c42eb63 --- /dev/null +++ b/Reader/Data/ValidationDescriptor.cs @@ -0,0 +1,44 @@ +using Img2Ffu.Reader.Structs; +using System.Collections.Generic; +using System.IO; + +namespace Img2Ffu.Reader.Data +{ + internal class ValidationDescriptor + { + public ValidationEntry ValidationEntry; + public byte[] ValidationBytes; + + public ValidationDescriptor(Stream stream) + { + ValidationEntry = stream.ReadStructure(); + + ValidationBytes = new byte[ValidationEntry.dwByteCount]; + _ = stream.Read(ValidationBytes, 0, (int)ValidationEntry.dwByteCount); + } + + public ValidationDescriptor(ValidationEntry validationEntry, byte[] validationBytes) + { + ValidationEntry = validationEntry; + ValidationBytes = validationBytes; + ValidationEntry.dwByteCount = (uint)validationBytes.LongLength; + } + + public override string ToString() + { + return $"{{ValidationEntry: {ValidationEntry}}}"; + } + + public byte[] GetBytes() + { + List bytes = []; + + ValidationEntry.dwByteCount = (uint)ValidationBytes.Length; + + bytes.AddRange(ValidationEntry.GetBytes()); + bytes.AddRange(ValidationBytes); + + return [.. bytes]; + } + } +} diff --git a/Reader/Data/WriteDescriptor.cs b/Reader/Data/WriteDescriptor.cs new file mode 100644 index 0000000..559896f --- /dev/null +++ b/Reader/Data/WriteDescriptor.cs @@ -0,0 +1,79 @@ +using Img2Ffu.Reader.Enums; +using Img2Ffu.Reader.Structs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Img2Ffu.Reader.Data +{ + internal class WriteDescriptor + { + public BlockDataEntry BlockDataEntry; + public uint DataSize; + + public readonly List DiskLocations = []; + private readonly bool HasDataSizeField; + + public WriteDescriptor(Stream stream, FFUVersion ffuVersion) + { + HasDataSizeField = ffuVersion == FFUVersion.V1_COMPRESSED; + + BlockDataEntry = stream.ReadStructure(); + + if (HasDataSizeField) + { + using BinaryReader binaryReader = new(stream, Encoding.ASCII, true); + DataSize = binaryReader.ReadUInt32(); + } + + for (uint i = 0; i < BlockDataEntry.LocationCount; i++) + { + DiskLocations.Add(stream.ReadStructure()); + } + } + + public WriteDescriptor(BlockDataEntry blockDataEntry, List diskLocations, uint compressedDataBlockSize) + { + HasDataSizeField = true; + + BlockDataEntry = blockDataEntry; + DiskLocations = diskLocations; + DataSize = compressedDataBlockSize; + } + + public WriteDescriptor(BlockDataEntry blockDataEntry, List diskLocations, FFUVersion ffuVersion) + { + HasDataSizeField = false; + + BlockDataEntry = blockDataEntry; + DiskLocations = diskLocations; + } + + public override string ToString() + { + return $"{{BlockDataEntry: {BlockDataEntry}}}"; + } + + public byte[] GetBytes() + { + List bytes = []; + + BlockDataEntry.LocationCount = (uint)DiskLocations.Count; + + bytes.AddRange(BlockDataEntry.GetBytes()); + + if (HasDataSizeField) + { + bytes.AddRange(BitConverter.GetBytes(DataSize)); + } + + foreach (DiskLocation diskLocation in DiskLocations) + { + bytes.AddRange(diskLocation.GetBytes()); + } + + return [.. bytes]; + } + } +} \ No newline at end of file diff --git a/Reader/Enums/CompressionAlgorithm.cs b/Reader/Enums/CompressionAlgorithm.cs new file mode 100644 index 0000000..93423ac --- /dev/null +++ b/Reader/Enums/CompressionAlgorithm.cs @@ -0,0 +1,11 @@ +namespace Img2Ffu.Reader.Enums +{ + public enum CompressionAlgorithm + { + None = 0, + Default = 1, + LZNT1 = 2, + XPRESS = 3, + XPRESS_HUFF = 4 + } +} diff --git a/Reader/Enums/DiskAccessMethod.cs b/Reader/Enums/DiskAccessMethod.cs new file mode 100644 index 0000000..5fbf7c6 --- /dev/null +++ b/Reader/Enums/DiskAccessMethod.cs @@ -0,0 +1,8 @@ +namespace Img2Ffu.Reader.Enums +{ + public enum DiskAccessMethod + { + DISK_BEGIN = 0, + DISK_END = 2 + } +} diff --git a/Reader/Enums/FFUVersion.cs b/Reader/Enums/FFUVersion.cs new file mode 100644 index 0000000..80bd67c --- /dev/null +++ b/Reader/Enums/FFUVersion.cs @@ -0,0 +1,9 @@ +namespace Img2Ffu.Reader.Enums +{ + internal enum FFUVersion + { + V1, + V1_COMPRESSED, + V2 + } +} \ No newline at end of file diff --git a/Reader/StructExtensions.cs b/Reader/StructExtensions.cs new file mode 100644 index 0000000..dfebd5e --- /dev/null +++ b/Reader/StructExtensions.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader +{ + public static class StructExtensions + { + public static T GetStruct(this byte[] bytes) where T : struct + { + int requiredSize = Marshal.SizeOf(typeof(T)); + if (bytes.Length < requiredSize) + { + throw new Exception($"Invalid buffer size for passed in structure. Expected: {requiredSize} Provided: {bytes.Length}"); + } + + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + T structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + handle.Free(); + return structure; + } + + public static byte[] GetBytes(this T structure) where T : struct + { + byte[] bytes = new byte[Marshal.SizeOf(typeof(T))]; + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), false); + handle.Free(); + return bytes; + } + + public static T Read(this BinaryReader reader) where T : struct + { + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + return bytes.GetStruct(); + } + + public static void Write(this BinaryWriter writer, T structure) where T : struct + { + byte[] bytes = structure.GetBytes(); + writer.Write(bytes); + } + + public static T ReadStructure(this Stream stream) where T : struct + { + using BinaryReader binaryReader = new(stream, System.Text.Encoding.ASCII, true); + return binaryReader.Read(); + } + + public static void WriteStructure(this Stream stream, T structure) where T : struct + { + using BinaryWriter binaryWriter = new(stream, System.Text.Encoding.ASCII, true); + binaryWriter.Write(structure); + } + } +} \ No newline at end of file diff --git a/Reader/Structs/BlockDataEntry.cs b/Reader/Structs/BlockDataEntry.cs new file mode 100644 index 0000000..ae79ca5 --- /dev/null +++ b/Reader/Structs/BlockDataEntry.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BlockDataEntry + { + public uint LocationCount; + public uint BlockCount; + + public override readonly string ToString() + { + return $"{{LocationCount: {LocationCount}, BlockCount: {BlockCount}}}"; + } + } +} diff --git a/Reader/Structs/DiskLocation.cs b/Reader/Structs/DiskLocation.cs new file mode 100644 index 0000000..958ae11 --- /dev/null +++ b/Reader/Structs/DiskLocation.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DiskLocation + { + public uint DiskAccessMethod; + public uint BlockIndex; + + public override readonly string ToString() + { + return $"{{DiskAccessMethod: {DiskAccessMethod}, BlockIndex: {BlockIndex}}}"; + } + } +} \ No newline at end of file diff --git a/Reader/Structs/ImageHeader.cs b/Reader/Structs/ImageHeader.cs new file mode 100644 index 0000000..7f9dc26 --- /dev/null +++ b/Reader/Structs/ImageHeader.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct ImageHeader + { + public uint Size; // sizeof(ImageHeader) + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)] + public string Signature; // "ImageFlash " + public uint ManifestLength; // in bytes + public uint ChunkSize; // Used only during image generation. + } +} \ No newline at end of file diff --git a/Reader/Structs/SecurityHeader.cs b/Reader/Structs/SecurityHeader.cs new file mode 100644 index 0000000..c15572c --- /dev/null +++ b/Reader/Structs/SecurityHeader.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct SecurityHeader + { + public uint Size; // size of struct, overall + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)] + public string Signature; // "SignedImage " + public uint ChunkSizeInKB; // size of a hashed chunk within the image + public uint AlgorithmId; // algorithm used to hash + public uint CatalogSize; // size of catalog to validate + public uint HashTableSize; // size of hash table + } +} \ No newline at end of file diff --git a/Reader/Structs/StoreHeader.cs b/Reader/Structs/StoreHeader.cs new file mode 100644 index 0000000..66b8c3e --- /dev/null +++ b/Reader/Structs/StoreHeader.cs @@ -0,0 +1,30 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct StoreHeader + { + public uint UpdateType; // indicates partial or full flash + public ushort MajorVersion, MinorVersion; // used to validate struct + public ushort FullFlashMajorVersion, FullFlashMinorVersion; // FFU version, i.e. the image format + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)] + public byte[] PlatformId; // string which indicates what device this FFU is intended to be written to + public uint BlockSize; // size of an image block in bytes – the device’s actual sector size may differ + public uint WriteDescriptorCount; // number of write descriptors to iterate through + public uint WriteDescriptorLength; // total size of all the write descriptors, in bytes (included so they can be read out up front and interpreted later) + public uint ValidateDescriptorCount; // number of validation descriptors to check + public uint ValidateDescriptorLength; // total size of all the validation descriptors, in bytes + public uint InitialTableIndex; // block index in the payload of the initial (invalid) GPT + public uint InitialTableCount; // count of blocks for the initial GPT, i.e. the GPT spans blockArray[idx..(idx + count -1)] + public uint FlashOnlyTableIndex; // first block index in the payload of the flash-only GPT (included so safe flashing can be accomplished) + public uint FlashOnlyTableCount; // count of blocks in the flash-only GPT + public uint FinalTableIndex; // index in the table of the real GPT + public uint FinalTableCount; // number of blocks in the real GPT + + public override readonly string ToString() + { + return $"{{UpdateType: {UpdateType}, MajorVersion: {MajorVersion}, MinorVersion: {MinorVersion}}}"; + } + } +} \ No newline at end of file diff --git a/Reader/Structs/StoreHeaderV2.cs b/Reader/Structs/StoreHeaderV2.cs new file mode 100644 index 0000000..15fe042 --- /dev/null +++ b/Reader/Structs/StoreHeaderV2.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + public struct StoreHeaderV2 + { + public ushort NumberOfStores; // Total number of stores (V2 only) + public ushort StoreIndex; // Current store index, 1-based (V2 only) + public ulong StorePayloadSize; // Payload data only, excludes padding (V2 only) + public ushort DevicePathLength; // Length of the device path (V2 only) + + public override readonly string ToString() + { + return $"{{NumberOfStores: {NumberOfStores}, StoreIndex: {StoreIndex}, StorePayloadSize: {StorePayloadSize}, DevicePathLength: {DevicePathLength}}}"; + } + } +} \ No newline at end of file diff --git a/Reader/Structs/ValidationEntry.cs b/Reader/Structs/ValidationEntry.cs new file mode 100644 index 0000000..ab67289 --- /dev/null +++ b/Reader/Structs/ValidationEntry.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Img2Ffu.Reader.Structs +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ValidationEntry + { + public uint dwSectorIndex; + public uint dwSectorOffset; + public uint dwByteCount; + } +} \ No newline at end of file diff --git a/UnifiedFlashingPlatform.csproj b/UnifiedFlashingPlatform.csproj index b0e0e53..a8e1b04 100644 --- a/UnifiedFlashingPlatform.csproj +++ b/UnifiedFlashingPlatform.csproj @@ -1,7 +1,7 @@ - net6.0-windows + net8.0-windows enable diff --git a/UnifiedFlashingPlatformTransport.WPI.cs b/UnifiedFlashingPlatformTransport.WPI.cs index 29dca41..c6449cb 100644 --- a/UnifiedFlashingPlatformTransport.WPI.cs +++ b/UnifiedFlashingPlatformTransport.WPI.cs @@ -1,11 +1,22 @@ -using System; +using Img2Ffu.Reader.Data; +using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; namespace UnifiedFlashingPlatform { + internal enum FfuProtocol + { + ProtocolSyncV1 = 1, + ProtocolAsyncV1 = 2, + ProtocolSyncV2 = 4, + ProtocolAsyncV2 = 8, + ProtocolAsyncV3 = 16 + } + public partial class UnifiedFlashingPlatformTransport { private readonly PhoneInfo Info = new(); @@ -123,6 +134,364 @@ namespace UnifiedFlashingPlatform return new GPT(GPTBuffer, SectorSize); // NOKT message header and MBR are ignored } + private void ThrowFlashError(int ErrorCode) + { + string SubMessage = ErrorCode switch + { + 0x0008 => "Unsupported protocol / Invalid options", + 0x000F => "Invalid sub block count", + 0x0010 => "Invalid sub block length", + 0x0012 => "Authentication required", + 0x000E => "Invalid sub block type", + 0x0013 => "Failed async message", + 0x1000 => "Invalid header type", + 0x1001 => "FFU header contain unknown extra data", + 0x0001 => "Couldn't allocate memory", + 0x1106 => "Security header validation failed", + 0x1105 => "Invalid hash table size", + 0x1104 => "Invalid catalog size", + 0x1103 => "Invalid chunk size", + 0x1102 => "Unsupported algorithm", + 0x1101 => "Invalid struct size", + 0x1100 => "Invalid signature", + 0x1202 => "Invalid struct size", + 0x1203 => "Unsupported algorithm", + 0x1204 => "Invalid chunk size", + 0x1005 => "Data not aligned correctly", + 0x0009 => "Locate protocol failed", + 0x1003 => "Hash mismatch", + 0x1006 => "Couldn't find hash from security header for index", + 0x1004 => "Security header import missing / All FFU headers have not been imported", + 0x1304 => "Invalid platform ID", + 0x1307 => "Invalid write descriptor info", + 0x1306 => "Invalid write descriptor info", + 0x1305 => "Invalid block size", + 0x1303 => "Unsupported FFU version", + 0x1302 => "Unsupported struct version", + 0x1301 => "Invalid update type", + 0x100B => "Too much payload data, all data has already been written", + 0x1008 => "Internal error", + 0x1007 => "Payload data does not contain all data", + 0x0004 => "Flash write failed", + 0x000D => "Flash verify failed", + 0x0002 => "Flash read failed", + _ => "Unknown error", + }; + WPinternalsException Ex = new("Flash failed!"); + Ex.SubMessage = "Error 0x" + ErrorCode.ToString("X4") + ": " + SubMessage; + + throw Ex; + } + + public void SendFfuHeaderV1(byte[] FfuHeader, byte Options = 0) + { + byte[] Request = new byte[FfuHeader.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes(0x0001, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = 0; // Progress = 0% + Request[0x0B] = 1; // Subblock count = 1 + Buffer.BlockCopy(BigEndian.GetBytes(0x0000000B, 4), 0, Request, 0x0C, 4); // Subblock type for header = 0x0B + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of header + 0x0C + Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x18, 4); // Payload length = length of header + Request[0x1C] = Options; // Header options = 0 + + Buffer.BlockCopy(FfuHeader, 0, Request, 0x20, FfuHeader.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuHeaderV2(uint TotalHeaderLength, uint OffsetForThisPart, byte[] FfuHeader, byte Options = 0) + { + byte[] Request = new byte[FfuHeader.Length + 0x3C]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = 0; // Progress = 0% + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x00000021, 4), 0, Request, 0x0C, 4); // Subblock type for header v2 = 0x21 + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length + 0x28, 4), 0, Request, 0x10, 4); // Subblock starts at 0x14, payload starts at 0x3C. + + Buffer.BlockCopy(BigEndian.GetBytes(0x00000000, 4), 0, Request, 0x14, 4); // Header type = 0 + Buffer.BlockCopy(BigEndian.GetBytes(TotalHeaderLength, 4), 0, Request, 0x18, 4); // Payload length = length of header + Request[0x1C] = Options; // Header options = 0 + + Buffer.BlockCopy(BigEndian.GetBytes(OffsetForThisPart, 4), 0, Request, 0x1D, 4); + Buffer.BlockCopy(BigEndian.GetBytes(FfuHeader.Length, 4), 0, Request, 0x21, 4); + Request[0x25] = 0; // No Erase + + Buffer.BlockCopy(FfuHeader, 0, Request, 0x3C, FfuHeader.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + if (Response.Length == 4) + { + throw new WPinternalsException("Flash protocol v2 not supported", "The device reported that the Flash protocol v2 was not supported while sending the FFU header."); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV1(byte[] FfuChunk, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x1C]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV1, 2), 0, Request, 0x06, 2); // Protocol version = 0x0001 + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000000C, 4), 0, Request, 0x0C, 4); // Subblock type for ChunkData = 0x0C + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x08, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x1C, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV2(byte[] FfuChunk, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolSyncV2, 2), 0, Request, 0x06, 2); // Protocol + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000001B, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x0C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x20, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public void SendFfuPayloadV3(byte[] FfuChunk, uint WriteDescriptorIndex, uint CRC, int Progress = 0, byte Options = 0) + { + byte[] Request = new byte[FfuChunk.Length + 0x20]; + + const string Header = "NOKXFS"; + Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Header), 0, Request, 0, Header.Length); + Buffer.BlockCopy(BigEndian.GetBytes((int)FfuProtocol.ProtocolAsyncV3, 2), 0, Request, 0x06, 2); // Protocol + Request[0x08] = (byte)Progress; // Progress = 0% (0 - 100) + Request[0x0B] = 1; // Subblock count = 1 + + Buffer.BlockCopy(BigEndian.GetBytes(0x0000001D, 4), 0, Request, 0x0C, 4); // Subblock type for Payload v2 = 0x1B + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length + 0x2C, 4), 0, Request, 0x10, 4); // Subblock length = length of chunk + 0x08 + + Buffer.BlockCopy(BigEndian.GetBytes(FfuChunk.Length, 4), 0, Request, 0x14, 4); // Payload length = length of chunk + Request[0x18] = Options; // Data options = 0 (1 = verify) + Buffer.BlockCopy(BigEndian.GetBytes(WriteDescriptorIndex, 4), 0, Request, 0x19, 4); // Payload length = length of chunk + Buffer.BlockCopy(BigEndian.GetBytes(CRC, 4), 0, Request, 0x1D, 4); // Payload length = length of chunk + + Buffer.BlockCopy(FfuChunk, 0, Request, 0x40, FfuChunk.Length); + + byte[] Response = ExecuteRawMethod(Request); + if (Response == null) + { + throw new BadConnectionException(); + } + + int ResultCode = (Response[6] << 8) + Response[7]; + if (ResultCode != 0) + { + ThrowFlashError(ResultCode); + } + } + + public class ProgressUpdater + { + private readonly DateTime InitTime; + private DateTime LastUpdateTime; + private readonly ulong MaxValue; + private readonly Action ProgressUpdateCallback; + public int ProgressPercentage; + + public ProgressUpdater(ulong MaxValue, Action ProgressUpdateCallback) + { + InitTime = DateTime.Now; + LastUpdateTime = DateTime.Now; + this.MaxValue = MaxValue; + this.ProgressUpdateCallback = ProgressUpdateCallback; + SetProgress(0); + } + + private ulong _Progress; + public ulong Progress + { + get + { + return _Progress; + } + } + + public void SetProgress(ulong NewValue) + { + if (_Progress != NewValue) + { + int PreviousProgressPercentage = (int)((double)_Progress / MaxValue * 100); + ProgressPercentage = (int)((double)NewValue / MaxValue * 100); + + _Progress = NewValue; + + if (((DateTime.Now - LastUpdateTime) > TimeSpan.FromSeconds(0.5)) || (ProgressPercentage == 100)) + { +#if DEBUG + Console.WriteLine("Init time: " + InitTime.ToShortTimeString() + " / Now: " + DateTime.Now.ToString() + " / NewValue: " + NewValue.ToString() + " / MaxValue: " + MaxValue.ToString() + " ->> Percentage: " + ProgressPercentage.ToString() + " / Remaining: " + TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue)))).ToString()); +#endif + + if (((DateTime.Now - InitTime) < TimeSpan.FromSeconds(30)) && (ProgressPercentage < 15)) + { + ProgressUpdateCallback(ProgressPercentage, null); + } + else + { + ProgressUpdateCallback(ProgressPercentage, TimeSpan.FromTicks((long)((DateTime.Now - InitTime).Ticks / ((double)NewValue / MaxValue) * (1 - ((double)NewValue / MaxValue))))); + } + + LastUpdateTime = DateTime.Now; + } + } + } + + public void IncreaseProgress(ulong Progress) + { + SetProgress(_Progress + Progress); + } + } + + public void FlashFFU(string FFUPath, bool ResetAfterwards = true, byte Options = 0) + { + using FileStream FfuFile = new(FFUPath, FileMode.Open, FileAccess.Read); + FlashFFU(FfuFile, ResetAfterwards, Options); + } + + public void FlashFFU(FileStream FfuFile, bool ResetAfterwards = true, byte Options = 0) + { + FlashFFU(FfuFile, null, ResetAfterwards, Options); + } + + public void FlashFFU(FileStream FfuFile, ProgressUpdater UpdaterPerChunk, bool ResetAfterwards = true, byte Options = 0) + { + ProgressUpdater Progress = UpdaterPerChunk; + + PhoneInfo Info = ReadPhoneInfo(); + if ((Info.SecureFfuSupportedProtocolMask & ((ushort)FfuProtocol.ProtocolSyncV1 | (ushort)FfuProtocol.ProtocolSyncV2)) == 0) + { + throw new WPinternalsException("Flash failed!", "Protocols not supported. The device reports that both Protocol Sync v1 and Protocol Sync v2 are not supported for FFU flashing. Is this an old device?"); + } + + long position = FfuFile.Position; + SignedImage FFU = new(FfuFile); + + int chunkSize = FFU.ChunkSize; + ulong totalChunkCount = FFU.TotalChunkCount; + ulong CombinedFFUHeaderSize = FFU.HeaderSize; + + FfuFile.Seek(position, SeekOrigin.Begin); + + byte[] FfuHeader = new byte[CombinedFFUHeaderSize]; + FfuFile.Read(FfuHeader, 0, (int)CombinedFFUHeaderSize); + SendFfuHeaderV1(FfuHeader, Options); + + ulong Position = CombinedFFUHeaderSize; + byte[] Payload; + int ChunkCount = 0; + + if ((Info.SecureFfuSupportedProtocolMask & (ushort)FfuProtocol.ProtocolSyncV2) == 0) + { + // Protocol v1 + Payload = new byte[chunkSize]; + + while (Position < (ulong)FfuFile.Length) + { + FfuFile.Read(Payload, 0, Payload.Length); + ChunkCount++; + SendFfuPayloadV1(Payload, (int)((double)ChunkCount * 100 / totalChunkCount), 0); + Position += (ulong)Payload.Length; + + Progress?.IncreaseProgress(1); + } + } + else + { + // Protocol v2 + Payload = new byte[Info.WriteBufferSize]; + + while (Position < (ulong)FfuFile.Length) + { + uint PayloadSize = Info.WriteBufferSize; + if (((ulong)FfuFile.Length - Position) < PayloadSize) + { + PayloadSize = (uint)((ulong)FfuFile.Length - Position); + Payload = new byte[PayloadSize]; + } + + FfuFile.Read(Payload, 0, (int)PayloadSize); + ChunkCount += (int)(PayloadSize / chunkSize); + SendFfuPayloadV2(Payload, (int)((double)ChunkCount * 100 / totalChunkCount), 0); + Position += PayloadSize; + + Progress?.IncreaseProgress((ulong)(PayloadSize / chunkSize)); + } + } + + if (ResetAfterwards) + { + ResetPhone(); + } + } + public PhoneInfo ReadPhoneInfo() { // NOKV = Info Query @@ -155,7 +524,7 @@ namespace UnifiedFlashingPlatform for (int i = 0; i < SubblockCount; i++) { byte SubblockID = Response[SubblockOffset + 0x00]; - UInt16 SubblockLength = BigEndian.ToUInt16(Response, SubblockOffset + 0x01); + ushort SubblockLength = BigEndian.ToUInt16(Response, SubblockOffset + 0x01); int SubblockPayloadOffset = SubblockOffset + 3; byte SubblockVersion; switch (SubblockID) @@ -285,13 +654,13 @@ namespace UnifiedFlashingPlatform public byte FlashAppProtocolVersionMajor; public byte FlashAppProtocolVersionMinor; - public UInt32 TransferSize; + public uint TransferSize; public bool MmosOverUsbSupported; - public UInt32 SdCardSizeInSectors; - public UInt32 WriteBufferSize; - public UInt32 EmmcSizeInSectors; + public uint SdCardSizeInSectors; + public uint WriteBufferSize; + public uint EmmcSizeInSectors; public string PlatformID; - public UInt16 SecureFfuSupportedProtocolMask; + public ushort SecureFfuSupportedProtocolMask; public bool AsyncSupport; public bool PlatformSecureBootEnabled; @@ -309,7 +678,7 @@ namespace UnifiedFlashingPlatform public string SKUNumber; public string BaseboardManufacturer; public string BaseboardProduct; - public UInt64 LargestMemoryRegion; + public ulong LargestMemoryRegion; public AppType AppType; public List<(uint SectorCount, uint SectorSize, ushort FlashType, ushort FlashIndex, uint Unknown, string DevicePath)> BootDevices = new();