mirror of
https://github.com/MobileTooling/UnifiedFlashingPlatform.git
synced 2026-07-27 12:48:47 -07:00
Enable Preliminary FFU support
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<Store> Stores = [];
|
||||
|
||||
private readonly long DataBlocksPosition;
|
||||
private readonly long InitialStreamPosition;
|
||||
|
||||
|
||||
public ImageFlash(Stream stream)
|
||||
{
|
||||
InitialStreamPosition = stream.Position;
|
||||
ImageHeader = stream.ReadStructure<ImageHeader>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<byte[]> BlockHashes = [];
|
||||
public X509Certificate Certificate;
|
||||
|
||||
private readonly Stream Stream;
|
||||
|
||||
public SignedImage(Stream stream)
|
||||
{
|
||||
Stream = stream;
|
||||
|
||||
SecurityHeader = stream.ReadStructure<SecurityHeader>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> ValidationDescriptor = [];
|
||||
public readonly List<WriteDescriptor> WriteDescriptors = [];
|
||||
public readonly List<string> PlatformIDs = [];
|
||||
public byte[] Padding = [];
|
||||
public readonly FFUVersion FFUVersion;
|
||||
|
||||
public Store(Stream stream)
|
||||
{
|
||||
StoreHeader = stream.ReadStructure<StoreHeader>();
|
||||
|
||||
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<StoreHeaderV2>();
|
||||
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}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<ValidationEntry>();
|
||||
|
||||
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<byte> bytes = [];
|
||||
|
||||
ValidationEntry.dwByteCount = (uint)ValidationBytes.Length;
|
||||
|
||||
bytes.AddRange(ValidationEntry.GetBytes());
|
||||
bytes.AddRange(ValidationBytes);
|
||||
|
||||
return [.. bytes];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<DiskLocation> DiskLocations = [];
|
||||
private readonly bool HasDataSizeField;
|
||||
|
||||
public WriteDescriptor(Stream stream, FFUVersion ffuVersion)
|
||||
{
|
||||
HasDataSizeField = ffuVersion == FFUVersion.V1_COMPRESSED;
|
||||
|
||||
BlockDataEntry = stream.ReadStructure<BlockDataEntry>();
|
||||
|
||||
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<DiskLocation>());
|
||||
}
|
||||
}
|
||||
|
||||
public WriteDescriptor(BlockDataEntry blockDataEntry, List<DiskLocation> diskLocations, uint compressedDataBlockSize)
|
||||
{
|
||||
HasDataSizeField = true;
|
||||
|
||||
BlockDataEntry = blockDataEntry;
|
||||
DiskLocations = diskLocations;
|
||||
DataSize = compressedDataBlockSize;
|
||||
}
|
||||
|
||||
public WriteDescriptor(BlockDataEntry blockDataEntry, List<DiskLocation> diskLocations, FFUVersion ffuVersion)
|
||||
{
|
||||
HasDataSizeField = false;
|
||||
|
||||
BlockDataEntry = blockDataEntry;
|
||||
DiskLocations = diskLocations;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{{BlockDataEntry: {BlockDataEntry}}}";
|
||||
}
|
||||
|
||||
public byte[] GetBytes()
|
||||
{
|
||||
List<byte> 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Img2Ffu.Reader.Enums
|
||||
{
|
||||
public enum CompressionAlgorithm
|
||||
{
|
||||
None = 0,
|
||||
Default = 1,
|
||||
LZNT1 = 2,
|
||||
XPRESS = 3,
|
||||
XPRESS_HUFF = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Img2Ffu.Reader.Enums
|
||||
{
|
||||
public enum DiskAccessMethod
|
||||
{
|
||||
DISK_BEGIN = 0,
|
||||
DISK_END = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Img2Ffu.Reader.Enums
|
||||
{
|
||||
internal enum FFUVersion
|
||||
{
|
||||
V1,
|
||||
V1_COMPRESSED,
|
||||
V2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Img2Ffu.Reader
|
||||
{
|
||||
public static class StructExtensions
|
||||
{
|
||||
public static T GetStruct<T>(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<T>(handle.AddrOfPinnedObject());
|
||||
handle.Free();
|
||||
return structure;
|
||||
}
|
||||
|
||||
public static byte[] GetBytes<T>(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<T>(this BinaryReader reader) where T : struct
|
||||
{
|
||||
byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));
|
||||
return bytes.GetStruct<T>();
|
||||
}
|
||||
|
||||
public static void Write<T>(this BinaryWriter writer, T structure) where T : struct
|
||||
{
|
||||
byte[] bytes = structure.GetBytes();
|
||||
writer.Write(bytes);
|
||||
}
|
||||
|
||||
public static T ReadStructure<T>(this Stream stream) where T : struct
|
||||
{
|
||||
using BinaryReader binaryReader = new(stream, System.Text.Encoding.ASCII, true);
|
||||
return binaryReader.Read<T>();
|
||||
}
|
||||
|
||||
public static void WriteStructure<T>(this Stream stream, T structure) where T : struct
|
||||
{
|
||||
using BinaryWriter binaryWriter = new(stream, System.Text.Encoding.ASCII, true);
|
||||
binaryWriter.Write(structure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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}}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -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<int, TimeSpan?> ProgressUpdateCallback;
|
||||
public int ProgressPercentage;
|
||||
|
||||
public ProgressUpdater(ulong MaxValue, Action<int, TimeSpan?> 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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user