diff --git a/Ffu2Vhdx.sln b/Ffu2Vhdx.sln index 6bc96ec..460fc7a 100644 --- a/Ffu2Vhdx.sln +++ b/Ffu2Vhdx.sln @@ -18,6 +18,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DiscUtils", "DiscUtils", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowsPhone", "WindowsPhone", "{6B6B2266-D083-4F82-9A0A-4A2D8D8F3F16}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FfuStream", "FfuStream\FfuStream.csproj", "{64DDFC5B-0C2F-4426-9DEE-1164292C0F25}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -152,6 +154,26 @@ Global {77D1F20E-4CD6-4810-9C5C-EF6CA3A9BC53}.Release|x64.Build.0 = Release|x64 {77D1F20E-4CD6-4810-9C5C-EF6CA3A9BC53}.Release|x86.ActiveCfg = Release|x86 {77D1F20E-4CD6-4810-9C5C-EF6CA3A9BC53}.Release|x86.Build.0 = Release|x86 + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|ARM32.ActiveCfg = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|ARM32.Build.0 = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|ARM64.Build.0 = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|x64.ActiveCfg = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|x64.Build.0 = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|x86.ActiveCfg = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Debug|x86.Build.0 = Debug|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|Any CPU.Build.0 = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|ARM32.ActiveCfg = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|ARM32.Build.0 = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|ARM64.ActiveCfg = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|ARM64.Build.0 = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|x64.ActiveCfg = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|x64.Build.0 = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|x86.ActiveCfg = Release|Any CPU + {64DDFC5B-0C2F-4426-9DEE-1164292C0F25}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ffu2Vhdx/Ffu2Vhdx.csproj b/Ffu2Vhdx/Ffu2Vhdx.csproj index 2056d89..92964ca 100644 --- a/Ffu2Vhdx/Ffu2Vhdx.csproj +++ b/Ffu2Vhdx/Ffu2Vhdx.csproj @@ -10,8 +10,7 @@ - - + diff --git a/Ffu2Vhdx/Program.cs b/Ffu2Vhdx/Program.cs index fe7f1d9..ce7a4bc 100644 --- a/Ffu2Vhdx/Program.cs +++ b/Ffu2Vhdx/Program.cs @@ -1,8 +1,7 @@ -using Microsoft.WindowsPhone.Imaging; -using DiscUtils; +using DiscUtils; using DiscUtils.Streams; using DiscUtils.Vhdx; -using System.Text; +using FfuStream; namespace Ffu2Vhdx { @@ -50,8 +49,59 @@ namespace Ffu2Vhdx using Stream fs = new FileStream(vhdfile, FileMode.CreateNew, FileAccess.ReadWrite); using VirtualDisk outDisk = Disk.InitializeDynamic(fs, Ownership.None, diskCapacity, logicalSectorSize: (int)ffuFile.GetSectorSize(i)); - ffuFile.WriteStoreToStream(i, outDisk.Content); + using Stream store = ffuFile.OpenStore(i); + + StreamPump pump = new() + { + InputStream = store, + OutputStream = outDisk.Content, + SparseCopy = true, + SparseChunkSize = (int)ffuFile.GetSectorSize(i), + BufferSize = (int)ffuFile.GetSectorSize(i) * 1024 + }; + + long totalBytes = store.Length; + + DateTime now = DateTime.Now; + pump.ProgressEvent += (o, e) => { ShowProgress((ulong)e.BytesRead, (ulong)totalBytes, now); }; + + Console.WriteLine($"Dumping Store {i}"); + pump.Run(); + Console.WriteLine(); + + store.Dispose(); } } + + protected static void ShowProgress(ulong readBytes, ulong totalBytes, DateTime startTime) + { + DateTime now = DateTime.Now; + TimeSpan timeSoFar = now - startTime; + + TimeSpan remaining = + TimeSpan.FromMilliseconds(timeSoFar.TotalMilliseconds / readBytes * (totalBytes - readBytes)); + + double speed = Math.Round(readBytes / 1024L / 1024L / timeSoFar.TotalSeconds); + + Console.Write( + $"\r{GetDismLikeProgBar((int)(readBytes * 100 / totalBytes))} {speed}MB/s {remaining:hh\\:mm\\:ss\\.f}"); + } + + private static string GetDismLikeProgBar(int percentage) + { + int eqsLength = (int)((double)percentage / 100 * 55); + string bases = new string('=', eqsLength) + new string(' ', 55 - eqsLength); + bases = bases.Insert(28, percentage + "%"); + if (percentage == 100) + { + bases = bases[1..]; + } + else if (percentage < 10) + { + bases = bases.Insert(28, " "); + } + + return $"[{bases}]"; + } } } \ No newline at end of file diff --git a/Ffu2Vhdx/FfuFile.cs b/FfuStream/FfuFile.cs similarity index 78% rename from Ffu2Vhdx/FfuFile.cs rename to FfuStream/FfuFile.cs index f648d9d..0d141f6 100644 --- a/Ffu2Vhdx/FfuFile.cs +++ b/FfuStream/FfuFile.cs @@ -1,8 +1,8 @@ using Microsoft.WindowsPhone.Imaging; -namespace Ffu2Vhdx +namespace FfuStream { - internal class FfuFile + public class FfuFile { private readonly FullFlashUpdateImage ffuImage; @@ -35,6 +35,19 @@ namespace Ffu2Vhdx payloadReader.WriteToStream(outputStream, storePayload, fullFlashUpdateStore.MinSectorCount, fullFlashUpdateStore.SectorSize); } + public Stream OpenStore(int StoreIndex) + { + if (StoreIndex >= StoreCount || StoreIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(StoreIndex)); + } + + FullFlashUpdateStore fullFlashUpdateStore = ffuImage.Stores[StoreIndex]; + StorePayload storePayload = payloadReader.Payloads[StoreIndex]; + + return new FfuStoreStream(payloadReader, fullFlashUpdateStore, storePayload); + } + public uint GetMinSectorCount(int StoreIndex) { if (StoreIndex >= StoreCount || StoreIndex < 0) diff --git a/FfuStream/FfuStoreStream.cs b/FfuStream/FfuStoreStream.cs new file mode 100644 index 0000000..4e83fb0 --- /dev/null +++ b/FfuStream/FfuStoreStream.cs @@ -0,0 +1,112 @@ +using Microsoft.WindowsPhone.Imaging; + +namespace FfuStream +{ + public partial class FfuStoreStream : Stream + { + private readonly PayloadReader payloadReader; + private readonly FullFlashUpdateStore fullFlashUpdateStore; + private readonly StorePayload storePayload; + private long position = 0; + + public FfuStoreStream(PayloadReader payloadReader, FullFlashUpdateStore fullFlashUpdateStore, StorePayload storePayload) + { + this.payloadReader = payloadReader; + this.fullFlashUpdateStore = fullFlashUpdateStore; + this.storePayload = storePayload; + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => fullFlashUpdateStore.MinSectorCount * fullFlashUpdateStore.SectorSize; + + public override long Position + { + get => position; + set => position = value; + } + + public override void Flush() + { + // Nothing to do! + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (position + count > Length) + { + count = (int)Length - (int)position; + } + + payloadReader.WriteToStream(storePayload, fullFlashUpdateStore.MinSectorCount, fullFlashUpdateStore.SectorSize, position, buffer, offset, count); + + position += count; + + return count; + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (offset > Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + switch (origin) + { + case SeekOrigin.Begin: + { + if (offset < 0) + { + throw new IOException(); + } + + position = offset; + break; + } + case SeekOrigin.Current: + { + int tempPosition = (int)position + (int)offset; + if (tempPosition < 0) + { + throw new IOException(); + } + + position = tempPosition; + break; + } + case SeekOrigin.End: + { + int tempPosition = (int)Length + (int)offset; + if (tempPosition < 0) + { + throw new IOException(); + } + + position = tempPosition; + break; + } + default: + { + throw new ArgumentException(nameof(origin)); + } + } + + return position; + } + + public override void SetLength(long value) + { + // Nothing to do! + } + + public override void Write(byte[] buffer, int offset, int count) + { + // Nothing to do! + } + } +} \ No newline at end of file diff --git a/FfuStream/FfuStream.csproj b/FfuStream/FfuStream.csproj new file mode 100644 index 0000000..6d7f6b6 --- /dev/null +++ b/FfuStream/FfuStream.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/ImageStorageServiceManaged/PayloadReader.cs b/ImageStorageServiceManaged/PayloadReader.cs index d6c2261..68ff52d 100644 --- a/ImageStorageServiceManaged/PayloadReader.cs +++ b/ImageStorageServiceManaged/PayloadReader.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -83,6 +84,81 @@ namespace Microsoft.WindowsPhone.Imaging } } + private static bool WriteDetourOk(long inputPosition, int inputCount, long position, int count) + { + long begin = Math.Max(inputPosition, position); + long end = Math.Min(inputPosition + inputCount, position + count); + + return end > begin; + } + + private static void WriteDetour(long inputPosition, byte[] inputBuffer, int inputCount, long position, byte[] buffer, int offset, int count) + { + long begin = Math.Max(inputPosition, position); + long end = Math.Min(inputPosition + inputCount, position + count); + + if (end > begin) + { + int index = (int)(begin - position) + offset; + int inputIndex = (int)(begin - inputPosition); + + using var strm = new MemoryStream(buffer); + strm.Seek(index, SeekOrigin.Begin); + strm.Write(inputBuffer, inputIndex, (int)(end - begin)); + } + } + + public void WriteToStream(StorePayload storePayload, ulong sectorCount, uint sectorSize, long position, byte[] buffer, int offset, int count) + { + uint bytesPerBlock = storePayload.StoreHeader.BytesPerBlock; + long totalSize = (long)(sectorCount * sectorSize); + + PayloadOffset payloadOffset = FindPayloadOffset(storePayload); + if (payloadOffset == null) + { + throw new ImageStorageException("Unable to find store payload."); + } + + _payloadStream.Position = payloadOffset.Offset; + + for (StorePayload.BlockPhase blockPhase = StorePayload.BlockPhase.Phase1; blockPhase != StorePayload.BlockPhase.Invalid; blockPhase++) + { + foreach (DataBlockEntry dataBlockEntry in storePayload.GetPhaseEntries(blockPhase)) + { + bool blockMatches = false; + byte[] ibuffer = new byte[bytesPerBlock]; + + for (int i = 0; i < dataBlockEntry.BlockLocationsOnDisk.Count; i++) + { + long ioffset = (long)(dataBlockEntry.BlockLocationsOnDisk[i].BlockIndex * (ulong)bytesPerBlock); + if (dataBlockEntry.BlockLocationsOnDisk[i].AccessMethod == DiskLocation.DiskAccessMethod.DiskEnd) + { + ioffset = totalSize - ioffset - (long)(ulong)bytesPerBlock; + } + + if (blockMatches) + { + WriteDetour(ioffset, ibuffer, (int)bytesPerBlock, position, buffer, offset, count); + } + else + { + blockMatches = WriteDetourOk(ioffset, (int)bytesPerBlock, position, count); + if (blockMatches) + { + _payloadStream.Read(ibuffer, 0, (int)bytesPerBlock); + WriteDetour(ioffset, ibuffer, (int)bytesPerBlock, position, buffer, offset, count); + } + } + } + + if (!blockMatches) + { + _payloadStream.Seek((int)bytesPerBlock, SeekOrigin.Current); + } + } + } + } + public ReadOnlyCollection Payloads { get