mirror of
https://github.com/MobileTooling/img2ffu.git
synced 2026-07-27 12:47:31 -07:00
380 lines
16 KiB
C#
380 lines
16 KiB
C#
/*
|
|
|
|
Copyright (c) 2019, Gustave Monce - gus33000.me - @gus33000
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
*/
|
|
using CommandLine;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace Img2Ffu
|
|
{
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
Parser.Default.ParseArguments<Options>(args).WithParsed(o =>
|
|
{
|
|
Logging.Log("img2ffu - Converts raw image (img) files into full flash update (FFU) files");
|
|
Logging.Log("Copyright (c) 2019, Gustave Monce - gus33000.me - @gus33000");
|
|
Logging.Log("Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda");
|
|
Logging.Log("Released as MIT license at github.com/gus33000/img2ffu");
|
|
Logging.Log("");
|
|
|
|
try
|
|
{
|
|
GenerateFFU(o.ImgFile, o.FfuFile, o.PlatId, o.ChunkSize, o.Antitheftver, o.Osversion);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logging.Log("Something happened.", Logging.LoggingLevel.Error);
|
|
Logging.Log(ex.Message, Logging.LoggingLevel.Error);
|
|
Logging.Log(ex.StackTrace, Logging.LoggingLevel.Error);
|
|
Environment.Exit(1);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void GenerateFFU(string ImageFile, string FFUFile, string PlatformId, UInt32 chunkSize, string AntiTheftVersion, string Osversion)
|
|
{
|
|
Logging.Log("Input image: " + ImageFile);
|
|
Logging.Log("Destination image: " + FFUFile);
|
|
Logging.Log("Platform ID: " + PlatformId);
|
|
Logging.Log("");
|
|
|
|
Stream stream;
|
|
|
|
if (ImageFile.ToLower().Contains(@"\\.\physicaldrive"))
|
|
stream = new DeviceStream(ImageFile, FileAccess.Read);
|
|
else
|
|
stream = new FileStream(ImageFile, FileMode.Open);
|
|
|
|
FileStream retstream = new FileStream(FFUFile, FileMode.CreateNew);
|
|
|
|
FlashPart[] flashParts = ImageSplitter.GetImageSlices(stream, chunkSize);
|
|
|
|
IOrderedEnumerable<FlashingPayload> payloads = FlashingPayloadGenerator.GetOptimizedPayloads(flashParts, chunkSize).OrderBy(x => x.TargetLocations.Count());
|
|
|
|
Logging.Log("");
|
|
Logging.Log("Building image headers...");
|
|
|
|
string header1 = Path.GetTempFileName();
|
|
FileStream Headerstream1 = new FileStream(header1, FileMode.OpenOrCreate);
|
|
|
|
// ==============================
|
|
// Header 1 start
|
|
|
|
ImageHeader image = new ImageHeader();
|
|
FullFlash ffimage = new FullFlash();
|
|
Store simage = new Store();
|
|
|
|
// Todo make this read the image itself
|
|
ffimage.OSVersion = Osversion;
|
|
ffimage.DevicePlatformId0 = PlatformId;
|
|
ffimage.AntiTheftVersion = AntiTheftVersion;
|
|
|
|
simage.SectorSize = 512;
|
|
simage.MinSectorCount = (UInt32)(stream.Length / 512);
|
|
|
|
Logging.Log("Generating image manifest...");
|
|
string manifest = ManifestIni.BuildUpManifest(ffimage, simage, new List<Partition>());
|
|
|
|
byte[] TextBytes = System.Text.Encoding.ASCII.GetBytes(manifest);
|
|
|
|
image.ManifestLength = (UInt32)TextBytes.Length;
|
|
|
|
byte[] ImageHeaderBuffer = new byte[0x18];
|
|
|
|
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0, image.Size);
|
|
ByteOperations.WriteAsciiString(ImageHeaderBuffer, 0x04, image.Signature);
|
|
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x10, image.ManifestLength);
|
|
ByteOperations.WriteUInt32(ImageHeaderBuffer, 0x14, image.ChunkSize);
|
|
|
|
Headerstream1.Write(ImageHeaderBuffer, 0, 0x18);
|
|
Headerstream1.Write(TextBytes, 0, TextBytes.Length);
|
|
|
|
RoundUpToChunks(Headerstream1, chunkSize);
|
|
|
|
// Header 1 stop + round
|
|
// ==============================
|
|
|
|
string header2 = Path.GetTempFileName();
|
|
FileStream Headerstream2 = new FileStream(header2, FileMode.OpenOrCreate);
|
|
|
|
// ==============================
|
|
// Header 2 start
|
|
|
|
StoreHeader store = new StoreHeader();
|
|
|
|
store.WriteDescriptorCount = (UInt32)payloads.Count();
|
|
store.FinalTableIndex = (UInt32)payloads.Count() - store.FinalTableCount;
|
|
store.PlatformId = PlatformId;
|
|
|
|
foreach (FlashingPayload payload in payloads)
|
|
{
|
|
store.WriteDescriptorLength += payload.GetStoreHeaderSize();
|
|
}
|
|
|
|
byte[] StoreHeaderBuffer = new byte[0xF8];
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0, store.UpdateType);
|
|
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x04, store.MajorVersion);
|
|
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x06, store.MinorVersion);
|
|
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x08, store.FullFlashMajorVersion);
|
|
ByteOperations.WriteUInt16(StoreHeaderBuffer, 0x0A, store.FullFlashMinorVersion);
|
|
ByteOperations.WriteAsciiString(StoreHeaderBuffer, 0x0C, store.PlatformId);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xCC, store.BlockSizeInBytes);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD0, store.WriteDescriptorCount);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD4, store.WriteDescriptorLength);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xD8, store.ValidateDescriptorCount);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xDC, store.ValidateDescriptorLength);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE0, store.InitialTableIndex);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE4, store.InitialTableCount);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xE8, store.FlashOnlyTableIndex);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xEC, store.FlashOnlyTableCount);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF0, store.FinalTableIndex);
|
|
ByteOperations.WriteUInt32(StoreHeaderBuffer, 0xF4, store.FinalTableCount);
|
|
Headerstream2.Write(StoreHeaderBuffer, 0, 0xF8);
|
|
|
|
byte[] descriptorsBuffer = new byte[store.WriteDescriptorLength];
|
|
|
|
UInt32 NewWriteDescriptorOffset = 0;
|
|
foreach (FlashingPayload payload in payloads)
|
|
{
|
|
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, (UInt32)payload.TargetLocations.Count()); // Location count
|
|
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, payload.ChunkCount); // Chunk count
|
|
NewWriteDescriptorOffset += 0x08;
|
|
|
|
foreach (UInt32 location in payload.TargetLocations)
|
|
{
|
|
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x00, 0x00000000); // Disk access method (0 = Begin, 2 = End)
|
|
ByteOperations.WriteUInt32(descriptorsBuffer, NewWriteDescriptorOffset + 0x04, location); // Chunk index
|
|
NewWriteDescriptorOffset += 0x08;
|
|
}
|
|
}
|
|
|
|
Headerstream2.Write(descriptorsBuffer, 0, (Int32)store.WriteDescriptorLength);
|
|
|
|
RoundUpToChunks(Headerstream2, chunkSize);
|
|
|
|
// Header 2 stop + round
|
|
// ==============================
|
|
|
|
SecurityHeader security = new SecurityHeader();
|
|
|
|
Headerstream1.Seek(0, SeekOrigin.Begin);
|
|
Headerstream2.Seek(0, SeekOrigin.Begin);
|
|
|
|
security.HashTableSize = 0x20 * (UInt32)((Headerstream1.Length + Headerstream2.Length) / chunkSize);
|
|
|
|
foreach (FlashingPayload payload in payloads)
|
|
{
|
|
security.HashTableSize += payload.GetSecurityHeaderSize();
|
|
}
|
|
|
|
byte[] HashTable = new byte[security.HashTableSize];
|
|
BinaryWriter bw = new BinaryWriter(new MemoryStream(HashTable));
|
|
|
|
SHA256 crypto = SHA256.Create();
|
|
for (int i = 0; i < Headerstream1.Length / chunkSize; i++)
|
|
{
|
|
byte[] buffer = new byte[chunkSize];
|
|
Headerstream1.Read(buffer, 0, (Int32)chunkSize);
|
|
byte[] hash = crypto.ComputeHash(buffer);
|
|
bw.Write(hash, 0, hash.Length);
|
|
}
|
|
|
|
for (int i = 0; i < Headerstream2.Length / chunkSize; i++)
|
|
{
|
|
byte[] buffer = new byte[chunkSize];
|
|
Headerstream2.Read(buffer, 0, (Int32)chunkSize);
|
|
byte[] hash = crypto.ComputeHash(buffer);
|
|
bw.Write(hash, 0, hash.Length);
|
|
}
|
|
|
|
foreach (FlashingPayload payload in payloads)
|
|
{
|
|
bw.Write(payload.ChunkHashes[0], 0, payload.ChunkHashes[0].Length);
|
|
}
|
|
|
|
bw.Close();
|
|
|
|
Logging.Log("Generating image catalog...");
|
|
byte[] catalog = GenerateCatalogFile(HashTable);
|
|
|
|
security.CatalogSize = (UInt32)catalog.Length;
|
|
|
|
byte[] SecurityHeaderBuffer = new byte[0x20];
|
|
|
|
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0, security.Size);
|
|
ByteOperations.WriteAsciiString(SecurityHeaderBuffer, 0x04, security.Signature);
|
|
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x10, security.ChunkSizeInKb);
|
|
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x14, security.HashAlgorithm);
|
|
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x18, security.CatalogSize);
|
|
ByteOperations.WriteUInt32(SecurityHeaderBuffer, 0x1C, security.HashTableSize);
|
|
|
|
retstream.Write(SecurityHeaderBuffer, 0, 0x20);
|
|
|
|
retstream.Write(catalog, 0, (Int32)security.CatalogSize);
|
|
retstream.Write(HashTable, 0, (Int32)security.HashTableSize);
|
|
|
|
RoundUpToChunks(retstream, chunkSize);
|
|
|
|
Headerstream1.Seek(0, SeekOrigin.Begin);
|
|
Headerstream2.Seek(0, SeekOrigin.Begin);
|
|
|
|
byte[] buff = new byte[Headerstream1.Length];
|
|
Headerstream1.Read(buff, 0, (Int32)Headerstream1.Length);
|
|
|
|
Headerstream1.Close();
|
|
File.Delete(header1);
|
|
|
|
retstream.Write(buff, 0, buff.Length);
|
|
|
|
buff = new byte[Headerstream2.Length];
|
|
Headerstream2.Read(buff, 0, (Int32)Headerstream2.Length);
|
|
|
|
Headerstream2.Close();
|
|
File.Delete(header2);
|
|
|
|
retstream.Write(buff, 0, buff.Length);
|
|
|
|
Logging.Log("Writing payloads...");
|
|
UInt64 counter = 0;
|
|
|
|
DateTime startTime = DateTime.Now;
|
|
|
|
foreach (FlashingPayload payload in payloads)
|
|
{
|
|
UInt32 StreamIndex = payload.StreamIndexes.First();
|
|
FlashPart flashPart = flashParts[StreamIndex];
|
|
Stream Stream = flashPart.Stream;
|
|
Stream.Seek(payload.StreamLocations.First(), SeekOrigin.Begin);
|
|
byte[] buffer = new byte[chunkSize];
|
|
Stream.Read(buffer, 0, (Int32)chunkSize);
|
|
retstream.Write(buffer, 0, (Int32)chunkSize);
|
|
counter++;
|
|
ShowProgress((UInt64)payloads.Count() * chunkSize, startTime, counter * chunkSize, counter * chunkSize);
|
|
}
|
|
|
|
retstream.Close();
|
|
Logging.Log("");
|
|
}
|
|
|
|
private static void RoundUpToChunks(Stream stream, UInt32 chunkSize)
|
|
{
|
|
Int64 Size = stream.Length;
|
|
if ((Size % chunkSize) > 0)
|
|
{
|
|
Int64 padding = (UInt32)(((Size / chunkSize) + 1) * chunkSize) - Size;
|
|
stream.Write(new byte[padding], 0, (Int32)padding);
|
|
}
|
|
}
|
|
|
|
private static void ShowProgress(ulong totalBytes, DateTime startTime, ulong BytesRead, ulong SourcePosition)
|
|
{
|
|
var now = DateTime.Now;
|
|
var timeSoFar = now - startTime;
|
|
|
|
var remaining = TimeSpan.FromMilliseconds(timeSoFar.TotalMilliseconds / BytesRead * (totalBytes - BytesRead));
|
|
|
|
var speed = Math.Round(SourcePosition / 1024L / 1024L / timeSoFar.TotalSeconds);
|
|
|
|
Logging.Log(string.Format("{0} {1}MB/s {2:hh\\:mm\\:ss\\.f}", GetDismLikeProgBar(int.Parse((BytesRead * 100 / totalBytes).ToString())), speed.ToString(), remaining, remaining.TotalHours, remaining.Minutes, remaining.Seconds, remaining.Milliseconds), returnline: false);
|
|
}
|
|
|
|
private static string GetDismLikeProgBar(int perc)
|
|
{
|
|
var eqsLength = (int)((double)perc / 100 * 55);
|
|
var bases = new string('=', eqsLength) + new string(' ', 55 - eqsLength);
|
|
bases = bases.Insert(28, perc + "%");
|
|
if (perc == 100)
|
|
bases = bases.Substring(1);
|
|
else if (perc < 10)
|
|
bases = bases.Insert(28, " ");
|
|
return "[" + bases + "]";
|
|
}
|
|
|
|
private static byte[] GenerateCatalogFile(byte[] hashData)
|
|
{
|
|
string catalog = Path.GetTempFileName();
|
|
string cdf = Path.GetTempFileName();
|
|
string hashTableBlob = Path.GetTempFileName();
|
|
|
|
File.WriteAllBytes(hashTableBlob, hashData);
|
|
|
|
using (StreamWriter streamWriter = new StreamWriter(cdf))
|
|
{
|
|
streamWriter.WriteLine("[CatalogHeader]");
|
|
streamWriter.WriteLine("Name={0}", catalog);
|
|
streamWriter.WriteLine("[CatalogFiles]");
|
|
streamWriter.WriteLine("{0}={1}", "HashTable.blob", hashTableBlob);
|
|
}
|
|
|
|
using (Process process = new Process())
|
|
{
|
|
process.StartInfo.FileName = "MakeCat.exe";
|
|
process.StartInfo.Arguments = string.Format("\"{0}\"", cdf);
|
|
process.StartInfo.UseShellExecute = false;
|
|
process.StartInfo.CreateNoWindow = true;
|
|
process.StartInfo.RedirectStandardOutput = true;
|
|
|
|
process.Start();
|
|
process.WaitForExit();
|
|
|
|
if (process.ExitCode != 0)
|
|
throw new Exception();
|
|
}
|
|
|
|
byte[] catalogBuffer = File.ReadAllBytes(catalog);
|
|
|
|
File.Delete(catalog);
|
|
File.Delete(hashTableBlob);
|
|
File.Delete(cdf);
|
|
|
|
return catalogBuffer;
|
|
}
|
|
|
|
internal class Options
|
|
{
|
|
[Option('i', "img-file", HelpText = @"A path to the img file to convert *OR* a PhysicalDisk path. i.e. \\.\PhysicalDrive1", Required = true)]
|
|
public string ImgFile { get; set; }
|
|
|
|
[Option('f', "ffu-file", HelpText = "A path to the FFU file to output", Required = true)]
|
|
public string FfuFile { get; set; }
|
|
|
|
[Option('p', "plat-id", HelpText = "Platform ID to use", Required = true)]
|
|
public string PlatId { get; set; }
|
|
|
|
[Option('a', "anti-theft-version", Required = false, HelpText = "Anti theft version.", Default = "1.1")]
|
|
public string Antitheftver { get; set; }
|
|
|
|
[Option('o', "os-version", Required = false, HelpText = "Operating system version.", Default = "10.0.11111.0")]
|
|
public string Osversion { get; set; }
|
|
|
|
[Option('c', "chunk-size", Required = false, HelpText = "Chunk size to use for the FFU file", Default = 131072u)]
|
|
public UInt32 ChunkSize { get; set; }
|
|
}
|
|
}
|
|
} |