Finalize a rudimentary implementation of Windows Image (WIM) archive format

This commit is contained in:
Michael Becker 2015-07-09 15:32:42 -04:00
parent 1193aa6636
commit 5d8fd37b6f
12 changed files with 408 additions and 43 deletions

View File

@ -6,6 +6,7 @@
<Filter Title="Windows Imaging archive">
<FileNameFilters>
<FileNameFilter>*.wim</FileNameFilter>
<FileNameFilter>*.swm</FileNameFilter>
</FileNameFilters>
<MagicByteSequences>
<MagicByteSequence>

View File

@ -117,6 +117,7 @@
<Content Include="Extensions\FileSystem\Associations\LHA.xml" />
<Content Include="Extensions\FileSystem\Associations\Microsoft\CompoundDocument.xml" />
<Content Include="Extensions\FileSystem\Associations\Microsoft\Cabinet.xml" />
<Content Include="Extensions\FileSystem\Associations\Microsoft\WindowsImage.xml" />
<Content Include="Extensions\FileSystem\Associations\Microsoft\MSCompressed.xml" />
<Content Include="Extensions\FileSystem\Associations\NeroDiscImage.xml" />
<Content Include="Extensions\FileSystem\Associations\PKZip.xml" />

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
[Flags()]
public enum WIMArchiveFlags : uint
{
None = 0x00000000,
Reserved = 0x00000001,
/// <summary>
/// Resources within the WIM (both file and metadata) are compressed.
/// </summary>
Compressed = 0x00000002,
/// <summary>
/// The contents of this WIM should not be changed.
/// </summary>
ReadOnly = 0x00000004,
/// <summary>
/// Resource data specified by the images within this WIM may be contained in another WIM.
/// </summary>
Spanned = 0x00000008,
/// <summary>
/// This WIM contains file resources only. It does not contain any file metadata.
/// </summary>
ResourceOnly = 0x00000010,
/// <summary>
/// This WIM contains file metadata only.
/// </summary>
MetadataOnly = 0x00000020,
/// <summary>
/// Limits one writer to the WIM file when opened with the WIM_FLAG_SHARE_WRITE mode. This flag is primarily used in the Windows Deployment Services (WDS) scenario.
/// </summary>
WriteInProgress = 0x00000040,
ReparsePointFixup = 0x00000080,
CompressReserved = 0x00010000,
/// <summary>
/// When <see cref="WIMArchiveFlags.Compressed" /> is set, resources within the wim are compressed using XPRESS compression.
/// </summary>
CompressedXpress = 0x00020000,
/// <summary>
/// When <see cref="WIMArchiveFlags.Compressed" /> is set, resources within the wim are compressed using LZX compression.
/// </summary>
CompressedLZX = 0x00040000
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public struct WIMArchiveHeader
{
/// <summary>
/// Signature that identifies the file as a .wim file. Value is set to “MSWIM\0\0”.
/// </summary>
public string magic;
/// <summary>
/// Size of the WIM header in bytes.
/// </summary>
public uint cbSize;
/// <summary>
/// The current version of the .wim file. This number will increase if the format of the .wim file changes.
/// </summary>
public uint dwVersion;
public WIMArchiveFlags dwFlags;
/// <summary>
/// Size of the compressed .wim file in bytes.
/// </summary>
public uint dwCompressionSize;
/// <summary>
/// A unique identifier for this WIM archive.
/// </summary>
public Guid gWIMGuid;
/// <summary>
/// The part number of the current .wim file in a spanned set. This value is 1, unless the data of the .wim file was split into multiple parts (.swm).
/// </summary>
public ushort usPartNumber;
/// <summary>
/// The total number of .wim file parts in a spanned set.
/// </summary>
public ushort usTotalParts;
/// <summary>
/// The number of images contained in the .wim file.
/// </summary>
public uint dwImageCount;
public WIMResourceHeaderDiskShort rhOffsetTable;
public WIMResourceHeaderDiskShort rhXmlData;
public WIMResourceHeaderDiskShort rhBootMetadata;
public uint dwBootIndex;
public WIMResourceHeaderDiskShort rhIntegrity;
public byte[/*60*/] bUnused;
}
}

View File

@ -25,56 +25,212 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
if (fsom == null) throw new ObjectModelNotSupportedException();
Reader reader = base.Accessor.Reader;
string magic = reader.ReadFixedLengthString(8);
if (magic != "MSWIM\0\0\0") throw new InvalidDataFormatException("File does not begin with 'MSWIM', 0x00, 0x00, 0x00");
WIMArchiveHeader header = ReadWIMArchiveHeader(reader);
uint offsetToFirstDataBlock = reader.ReadUInt32();
reader.Seek((long)header.rhOffsetTable.liOffset, SeekOrigin.Begin);
byte[] tblOffsetTable = reader.ReadBytes(header.rhOffsetTable.ullSize);
uint unknown2 = reader.ReadUInt32();
uint unknown3 = reader.ReadUInt32();
uint unknown4 = reader.ReadUInt32();
Guid guid = reader.ReadGuid();
ushort unknown5a = reader.ReadUInt16();
ushort unknown5b = reader.ReadUInt16();
uint unknown6 = reader.ReadUInt32();
uint unknown7 = reader.ReadUInt32();
uint unknown8 = reader.ReadUInt32();
uint unknown9 = reader.ReadUInt32();
uint unknown10 = reader.ReadUInt32();
uint unknown11 = reader.ReadUInt32();
uint unknown12 = reader.ReadUInt32();
uint unknown13 = reader.ReadUInt32();
uint unknown14 = reader.ReadUInt32();
ulong xmlDataOffset = reader.ReadUInt64();
ulong xmlDataLength = reader.ReadUInt64();
// file record
Reader offsetTableReader = new Reader(new Accessors.MemoryAccessor(tblOffsetTable));
List<WIMOffsetTableEntry> entries = new List<WIMOffsetTableEntry>();
while (!offsetTableReader.EndOfStream)
{
// 20 byte guid???
Guid fileGuid = reader.ReadGuid();
uint fileGuid2 = reader.ReadUInt32();
ulong unknownB1 = reader.ReadUInt64();
ulong unknownB2 = reader.ReadUInt64();
ushort fileNameLength = reader.ReadUInt16();
string fileName = reader.ReadFixedLengthString(fileNameLength, Encoding.UTF16LittleEndian);
WIMOffsetTableEntry entry = ReadWIMOffsetTableEntry(offsetTableReader);
entries.Add(entry);
}
// file data mapping record
WIMOffsetTableEntry lastEntry = entries[entries.Count - 1];
reader.Seek((long)lastEntry.liOffset, SeekOrigin.Begin);
int padRest = (int)((long)lastEntry.liOffset % 8);
Dictionary<byte[], WIMDirectoryEntry> direntries = new Dictionary<byte[], WIMDirectoryEntry>();
#region SECURITYBLOCK_DISK
{
uint unknownZ1 = reader.ReadUInt32();
ulong fileLength1 = reader.ReadUInt64(); // maybe compressed/uncompressed length?
ulong fileOffset = reader.ReadUInt64();
ulong fileLength = reader.ReadUInt64();
Guid fileGuid = reader.ReadGuid();
uint fileGuid2 = reader.ReadUInt32();
uint dwTotalLength = reader.ReadUInt32();
uint dwNumEntries = reader.ReadUInt32();
ulong[] liEntryLength = reader.ReadUInt64Array((int)dwNumEntries);
}
#endregion
#region DIRENTRY
{
WIMDirectoryEntry entry = default(WIMDirectoryEntry);
do
{
entry = ReadWIMDirectoryEntry(reader);
if (!entry.Equals(default(WIMDirectoryEntry)))
{
direntries.Add(entry.bHash, entry);
reader.Align(8, padRest);
}
else
{
reader.Align(8);
}
}
while (!entry.Equals(default(WIMDirectoryEntry)));
Dictionary<WIMDirectoryEntry, WIMOffsetTableEntry> offsetDictionary = new Dictionary<WIMDirectoryEntry, WIMOffsetTableEntry>();
foreach (WIMOffsetTableEntry ent in entries)
{
foreach (KeyValuePair<byte[], WIMDirectoryEntry> kvp in direntries)
{
if (kvp.Key.Match(ent.bHash))
{
offsetDictionary.Add(kvp.Value, ent);
}
}
}
foreach (KeyValuePair<WIMDirectoryEntry, WIMOffsetTableEntry> kvp in offsetDictionary)
{
File file = fsom.AddFile(kvp.Key.FileName);
file.Name = kvp.Key.FileName;
file.Size = (long)kvp.Value.liOriginalSize;
file.Properties.Add("offset", (long)kvp.Value.liOffset);
file.Properties.Add("length", (long)kvp.Value.liOriginalSize);
file.Properties.Add("reader", reader);
file.DataRequest += file_DataRequest;
}
}
#endregion
#region Load XML data - we don't actually use this anywhere... yet
{
reader.Seek((long)header.rhXmlData.liOffset, SeekOrigin.Begin);
string xmlData = reader.ReadFixedLengthString((long)header.rhXmlData.ullSize, Encoding.UTF16LittleEndian);
UniversalEditor.ObjectModels.Markup.MarkupObjectModel mom = new ObjectModels.Markup.MarkupObjectModel();
UniversalEditor.DataFormats.Markup.XML.XMLDataFormat xdf = new Markup.XML.XMLDataFormat();
Document.Load(mom, xdf, new Accessors.StringAccessor(xmlData));
fsom.CustomProperties.Add(typeof(UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage.WIMDataFormat), "XMLDescriptor", mom);
}
#endregion
}
private void file_DataRequest(object sender, DataRequestEventArgs e)
{
File file = (sender as File);
Reader reader = (Reader)file.Properties["reader"];
long offset = (long)file.Properties["offset"];
long length = (long)file.Properties["length"];
reader.Seek(offset, SeekOrigin.Begin);
byte[] compressedData = reader.ReadBytes(length);
byte[] uncompressedData = compressedData;
e.Data = uncompressedData;
}
private WIMDirectoryEntry ReadWIMDirectoryEntry(Reader lastEntryReader)
{
WIMDirectoryEntry item = new WIMDirectoryEntry();
item.liLength = lastEntryReader.ReadUInt64();
if (item.liLength == 0) return default(WIMDirectoryEntry);
item.dwAttributes = lastEntryReader.ReadUInt32();
item.dwSecurityId = lastEntryReader.ReadInt32();
item.liSubdirOffset = lastEntryReader.ReadUInt64();
item.liUnused1 = lastEntryReader.ReadUInt64();
item.liUnused2 = lastEntryReader.ReadUInt64();
item.liCreationTime = lastEntryReader.ReadUInt64();
item.liLastAccessTime = lastEntryReader.ReadUInt64();
item.liLastWriteTime = lastEntryReader.ReadUInt64();
item.bHash = lastEntryReader.ReadBytes(20);
item.dwReparseTag = lastEntryReader.ReadUInt32();
item.dwReparseReserved = lastEntryReader.ReadUInt32();
if ((item.dwAttributes & 16) == 16)
{
item.liHardLink = lastEntryReader.ReadUInt64();
}
else
{
item.liHardLink = lastEntryReader.ReadUInt32();
}
ushort wStreamCount = lastEntryReader.ReadUInt16();
item.wStreams = new WIMStreamEntry[wStreamCount];
item.wShortNameLength = lastEntryReader.ReadUInt16();
ushort wFileNameLength = lastEntryReader.ReadUInt16();
item.FileName = lastEntryReader.ReadFixedLengthString(wFileNameLength, Encoding.UTF16LittleEndian);
for (ushort i = 0; i < wStreamCount; i++)
{
item.wStreams[i] = ReadWIMStreamEntry(lastEntryReader);
}
ulong realLength = (ulong)((8 * 10) + 20 + 4 + wFileNameLength);
if (item.liLength != realLength)
{
}
return item;
}
private WIMStreamEntry ReadWIMStreamEntry(Reader reader)
{
WIMStreamEntry item = new WIMStreamEntry();
item.liLength = reader.ReadUInt64();
item.Unused1 = reader.ReadUInt64();
item.bHash = reader.ReadBytes(20);
item.wStreamNameLength = reader.ReadUInt16();
item.StreamName = reader.ReadFixedLengthString(item.wStreamNameLength, Encoding.UTF16LittleEndian);
return item;
}
private WIMOffsetTableEntry ReadWIMOffsetTableEntry(Reader reader)
{
WIMResourceHeaderDiskShort reshdr = ReadWIMResourceHeaderDiskShort(reader);
WIMOffsetTableEntry wim = new WIMOffsetTableEntry();
wim.flags = reshdr.flags;
wim.liOffset = reshdr.liOffset;
wim.liOriginalSize = reshdr.liOriginalSize;
wim.ullSize = reshdr.ullSize;
wim.usPartNumber = reader.ReadUInt16();
wim.dwRefCount = reader.ReadUInt32();
wim.bHash = reader.ReadBytes(20);
return wim;
}
private WIMArchiveHeader ReadWIMArchiveHeader(Reader reader)
{
WIMArchiveHeader header = new WIMArchiveHeader();
header.magic = reader.ReadFixedLengthString(8);
if (header.magic != "MSWIM\0\0\0") throw new InvalidDataFormatException("File does not begin with 'MSWIM', 0x00, 0x00, 0x00");
header.cbSize = reader.ReadUInt32();
header.dwVersion = reader.ReadUInt32();
header.dwFlags = (WIMArchiveFlags)reader.ReadUInt32();
header.dwCompressionSize = reader.ReadUInt32();
header.gWIMGuid = reader.ReadGuid();
header.usPartNumber = reader.ReadUInt16();
header.usTotalParts = reader.ReadUInt16();
header.dwImageCount = reader.ReadUInt32();
header.rhOffsetTable = ReadWIMResourceHeaderDiskShort(reader);
header.rhXmlData = ReadWIMResourceHeaderDiskShort(reader);
header.rhBootMetadata = ReadWIMResourceHeaderDiskShort(reader);
header.dwBootIndex = reader.ReadUInt32();
header.rhIntegrity = ReadWIMResourceHeaderDiskShort(reader);
header.bUnused = reader.ReadBytes(60);
return header;
}
private WIMResourceHeaderDiskShort ReadWIMResourceHeaderDiskShort(Reader reader)
{
WIMResourceHeaderDiskShort item = new WIMResourceHeaderDiskShort();
long sizeAndFlags = reader.ReadInt64();
ulong size = (ulong)(sizeAndFlags << 8);
size = (ulong)(size >> 8);
byte flags = (byte)(sizeAndFlags >> 56);
item.ullSize = size;
item.flags = (WIMResourceHeaderDiskFlags)flags;
item.liOffset = reader.ReadUInt64();
item.liOriginalSize = reader.ReadUInt64();
return item;
}
protected override void SaveInternal(ObjectModel objectModel)

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public struct WIMDirectoryEntry
{
public ulong liLength;
public uint dwAttributes;
public int dwSecurityId;
public ulong liSubdirOffset;
public ulong liUnused1;
public ulong liUnused2;
public ulong liCreationTime;
public ulong liLastAccessTime;
public ulong liLastWriteTime;
public byte[/*20*/] bHash;
public uint dwReparseTag;
public uint dwReparseReserved;
public ulong liHardLink;
public ushort wShortNameLength;
public string FileName;
public WIMStreamEntry[] wStreams;
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public class WIMOffsetTableEntry : WIMResourceHeaderDiskShort
{
public ushort usPartNumber;
public uint dwRefCount;
public byte[/*20*/] bHash;
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public class WIMResourceHeaderDisk
{
public ulong ullSize;
public WIMResourceHeaderDiskFlags flags;
public ulong liOffset;
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
[Flags()]
public enum WIMResourceHeaderDiskFlags
{
None = 0x00,
Free = 0x01,
Metadata = 0x02,
Compressed = 0x04,
Spanned = 0x08
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public class WIMResourceHeaderDiskShort : WIMResourceHeaderDisk
{
public ulong liOriginalSize;
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.WindowsImage
{
public struct WIMStreamEntry
{
public ulong liLength;
public ulong Unused1;
public byte[/*20*/] bHash;
public ushort wStreamNameLength;
public string StreamName;
}
}

View File

@ -50,6 +50,15 @@
<Compile Include="DataFormats\FileSystem\Microsoft\MSCompressed\MSCompressedDataFormat.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\MSCompressed\MSCompressedKWAJCompressionMethod.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\MSCompressed\MSCompressedKWAJHeaderFlags.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMArchiveFlags.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMArchiveHeader.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMDataFormat.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMDirectoryEntry.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMOffsetTableEntry.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMResourceHeaderDisk.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMResourceHeaderDiskFlags.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMResourceHeaderDiskShort.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\WindowsImage\WIMStreamEntry.cs" />
<Compile Include="DataFormats\Shortcut\Microsoft\LNKDataFlags.cs" />
<Compile Include="DataFormats\Shortcut\Microsoft\LNKDataFormat.cs" />
<Compile Include="DataFormats\Shortcut\Microsoft\LNKFileAttributeFlags.cs" />