-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
158 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
using CSharpFunctionalExtensions; | ||
using System.IO; | ||
using CSharpFunctionalExtensions; | ||
|
||
namespace Deployer.Wim | ||
{ | ||
public interface IWindowsImageMetadataReader | ||
{ | ||
Result<XmlWindowsImageMetadata> Load(string path); | ||
Result<XmlWindowsImageMetadata> Load(Stream stream); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,138 +1,187 @@ | ||
using System; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Runtime.InteropServices; | ||
using System.Xml.Serialization; | ||
using CSharpFunctionalExtensions; | ||
using ManagedWimLib; | ||
using Serilog; | ||
using Zafiro.Core.Pending; | ||
using Zafiro.System.Windows; | ||
|
||
namespace Deployer.Wim; | ||
|
||
public class WindowsImageMetadataReader : IWindowsImageMetadataReader | ||
namespace Deployer.Wim | ||
{ | ||
private static bool isInitialized; | ||
|
||
public WindowsImageMetadataReader() | ||
public class WindowsImageMetadataReader : IWindowsImageMetadataReader | ||
{ | ||
if (isInitialized) | ||
{ | ||
return; | ||
} | ||
|
||
InitNativeLibrary(); | ||
isInitialized = true; | ||
} | ||
|
||
private static XmlSerializer Serializer { get; } = new(typeof(WimMetadata)); | ||
private static XmlSerializer Serializer { get; } = new XmlSerializer(typeof(WimMetadata)); | ||
|
||
public Result<XmlWindowsImageMetadata> Load(string path) | ||
{ | ||
return Result.Try(() => LoadCore(path)); | ||
} | ||
|
||
private static void InitNativeLibrary() | ||
{ | ||
var libBaseDir = AppDomain.CurrentDomain.BaseDirectory; | ||
var libDir = "runtimes"; | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
{ | ||
libDir = Path.Combine(libDir, "win-"); | ||
} | ||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||
public Result<XmlWindowsImageMetadata> Load(Stream stream) | ||
{ | ||
libDir = Path.Combine(libDir, "linux-"); | ||
Log.Verbose("Getting WIM stream"); | ||
|
||
WimMetadata metadata; | ||
try | ||
{ | ||
metadata = (WimMetadata)Serializer.Deserialize(GetXmlMetadataStream(stream)); | ||
} | ||
catch (Exception e) | ||
{ | ||
return Result.Failure<XmlWindowsImageMetadata>("Could not read the metadata from the WIM " + | ||
$"file. Please, check it's a valid .WIM file: {e.Message}"); | ||
} | ||
|
||
Log.Verbose("Wim metadata deserialized correctly {@Metadata}", metadata); | ||
|
||
var images = from i in metadata.Images.Where(t => t.Windows is not null) | ||
from a in GetArchitecture(i.Windows.Arch).ToEnumerable() | ||
select new DiskImageMetadata | ||
{ | ||
Architecture = a, | ||
Build = i.Windows.Version.Build, | ||
DisplayName = i.Name, | ||
Index = int.Parse(i.Index) | ||
}; | ||
|
||
return new XmlWindowsImageMetadata | ||
{ | ||
Images = images.ToList(), | ||
}; | ||
} | ||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
|
||
private static Result<ProcessorArchitecture> GetArchitecture(string str) | ||
{ | ||
libDir = Path.Combine(libDir, "osx-"); | ||
switch (str) | ||
{ | ||
case "0": | ||
return ProcessorArchitecture.X86; | ||
case "9": | ||
return ProcessorArchitecture.Amd64; | ||
case "12": | ||
return ProcessorArchitecture.Arm64; | ||
} | ||
|
||
return Result.Failure<ProcessorArchitecture>($"Cannot find architecture '{str}' is unknown"); | ||
} | ||
|
||
switch (RuntimeInformation.ProcessArchitecture) | ||
protected Stream GetXmlMetadataStream(Stream wim) | ||
{ | ||
case Architecture.X86: | ||
libDir += "x86"; | ||
break; | ||
case Architecture.X64: | ||
libDir += "x64"; | ||
break; | ||
case Architecture.Arm: | ||
libDir += "arm"; | ||
break; | ||
case Architecture.Arm64: | ||
libDir += "arm64"; | ||
break; | ||
} | ||
var outputstream = new MemoryStream(); | ||
var wimwriter = new BinaryWriter(outputstream); | ||
using (var wimsecstream = wim) | ||
{ | ||
using (var wimsecreader = new BinaryReader(wimsecstream)) | ||
{ | ||
var bytes = new byte[] | ||
{ | ||
0x4D, 0x53, 0x57, 0x49, 0x4D | ||
}; | ||
|
||
libDir = Path.Combine(libDir, "native"); | ||
Log.Verbose("(WIM) Finding Magic Bytes..."); | ||
|
||
// Some platforms require native library custom path to be an absolute path. | ||
string libPath = null; | ||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
{ | ||
libPath = Path.Combine(libBaseDir, libDir, "libwim-15.dll"); | ||
} | ||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) | ||
{ | ||
libPath = Path.Combine(libBaseDir, libDir, "libwim.so"); | ||
} | ||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) | ||
{ | ||
libPath = Path.Combine(libBaseDir, libDir, "libwim.dylib"); | ||
var start = FindPosition(wimsecstream, bytes); | ||
|
||
Log.Verbose("(WIM) Found Magic Bytes at " + start); | ||
|
||
Log.Verbose("(WIM) Finding WIM XML Data..."); | ||
|
||
var endbytes = new byte[] | ||
{ | ||
0x3C, 0x00, 0x2F, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4D, 0x00, 0x3E, 0x00 | ||
}; | ||
|
||
wimsecstream.Seek(start + 72, SeekOrigin.Begin); | ||
var buffer = new byte[24]; | ||
wimsecstream.Read(buffer, 0, 24); | ||
var may = ToInt64LittleEndian(buffer, 8); | ||
wimsecstream.Seek(start, SeekOrigin.Begin); | ||
|
||
Log.Verbose("(WIM) Found WIM XML Data at " + start + may + 2); | ||
|
||
wimsecstream.Seek(start + may + 2, SeekOrigin.Begin); | ||
|
||
for (var i = wimsecstream.Position; i < wimsecstream.Length - endbytes.Length; i++) | ||
{ | ||
if (BitConverter.ToString(wimsecreader.ReadBytes(12)) == BitConverter.ToString(endbytes)) | ||
{ | ||
wimwriter.Write(endbytes); | ||
break; | ||
} | ||
|
||
wimsecstream.Seek(-12, SeekOrigin.Current); | ||
wimwriter.Write(wimsecreader.ReadBytes(1)); | ||
} | ||
} | ||
} | ||
|
||
outputstream.Seek(0, SeekOrigin.Begin); | ||
return outputstream; | ||
} | ||
|
||
if (libPath == null) | ||
private static long ToInt64LittleEndian(byte[] buffer, int offset) | ||
{ | ||
throw new PlatformNotSupportedException("Unable to find native library."); | ||
return (long)ToUInt64LittleEndian(buffer, offset); | ||
} | ||
|
||
if (!File.Exists(libPath)) | ||
private static uint ToUInt32LittleEndian(byte[] buffer, int offset) | ||
{ | ||
throw new PlatformNotSupportedException($"Unable to find native library [{libPath}]."); | ||
var a = (buffer[offset + 3] << 24) & 0xFF000000U; | ||
var b = (buffer[offset + 2] << 16) & 0x00FF0000U; | ||
var c = (buffer[offset + 1] << 8) & 0x0000FF00U; | ||
var d = (buffer[offset + 0] << 0) & 0x000000FFU; | ||
|
||
return (uint)(a | b | c | d); | ||
} | ||
|
||
ManagedWimLib.Wim.GlobalInit(libPath, InitFlags.None); | ||
} | ||
|
||
private static Result<ProcessorArchitecture> GetArchitecture(string str) | ||
{ | ||
switch (str) | ||
private static ulong ToUInt64LittleEndian(byte[] buffer, int offset) | ||
{ | ||
case "0": | ||
return ProcessorArchitecture.X86; | ||
case "9": | ||
return ProcessorArchitecture.Amd64; | ||
case "12": | ||
return ProcessorArchitecture.Arm64; | ||
return ((ulong)ToUInt32LittleEndian(buffer, offset + 4) << 32) | ToUInt32LittleEndian(buffer, offset + 0); | ||
} | ||
|
||
return Result.Failure<ProcessorArchitecture>($"Cannot find architecture '{str}' is unknown"); | ||
} | ||
|
||
private XmlWindowsImageMetadata LoadCore(string path) | ||
{ | ||
var xml = ManagedWimLib.Wim.OpenWim(path, OpenFlags.None).GetXmlData()[1..]; // Skip first (invalid) invisible char | ||
var stringReader = new StringReader(xml); | ||
// | ||
// https://stackoverflow.com/questions/1471975/best-way-to-find-position-in-the-stream-where-given-byte-sequence-starts | ||
// | ||
public static long FindPosition(Stream stream, byte[] byteSequence) | ||
{ | ||
if (byteSequence.Length > stream.Length) | ||
{ | ||
return -1; | ||
} | ||
|
||
var metadata = (WimMetadata) Serializer.Deserialize(stringReader); | ||
var buffer = new byte[byteSequence.Length]; | ||
|
||
Log.Verbose("Wim metadata deserialized correctly {@Metadata}", metadata); | ||
var bufStream = new BufferedStream(stream, byteSequence.Length); | ||
|
||
var images = from i in metadata.Images.Where(t => t.Windows is not null) | ||
from a in GetArchitecture(i.Windows.Arch).ToEnumerable() | ||
select new DiskImageMetadata | ||
while ((bufStream.Read(buffer, 0, byteSequence.Length)) == byteSequence.Length) | ||
{ | ||
Architecture = a, | ||
Build = i.Windows.Version.Build, | ||
DisplayName = i.Name, | ||
Index = int.Parse(i.Index) | ||
}; | ||
if (byteSequence.SequenceEqual(buffer)) | ||
{ | ||
return bufStream.Position - byteSequence.Length; | ||
} | ||
|
||
bufStream.Position -= byteSequence.Length - PadLeftSequence(buffer, byteSequence); | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
return new XmlWindowsImageMetadata | ||
private static int PadLeftSequence(byte[] bytes, byte[] seqBytes) | ||
{ | ||
Images = images.ToList() | ||
}; | ||
var i = 1; | ||
while (i < bytes.Length) | ||
{ | ||
var n = bytes.Length - i; | ||
var aux1 = new byte[n]; | ||
var aux2 = new byte[n]; | ||
Array.Copy(bytes, i, aux1, 0, n); | ||
Array.Copy(seqBytes, aux2, n); | ||
if (aux1.SequenceEqual(aux2)) | ||
{ | ||
return i; | ||
} | ||
|
||
i++; | ||
} | ||
|
||
return i; | ||
} | ||
} | ||
} |