Skip to content

Commit 7f29027

Browse files
committed
Nullable enable for DiscUtils.Wim
* Also added WimFile.TryGetImage to easier check for non-existent images within a WIM file.
1 parent 201c300 commit 7f29027

File tree

8 files changed

+134
-76
lines changed

8 files changed

+134
-76
lines changed

Library/DiscUtils.Wim/AlternateStreamEntry.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,27 @@
2020
// DEALINGS IN THE SOFTWARE.
2121
//
2222

23+
using System.Collections.Immutable;
24+
using System.Diagnostics.CodeAnalysis;
2325
using System.Text;
2426
using DiscUtils.Streams;
27+
using LTRData.Extensions.Buffers;
2528

2629
namespace DiscUtils.Wim;
2730

2831
internal class AlternateStreamEntry
2932
{
30-
public byte[] Hash;
33+
public ImmutableArray<byte> Hash;
3134
public long Length;
32-
public string Name;
35+
public string Name = null!;
3336

34-
public static AlternateStreamEntry ReadFrom(DataReader reader)
37+
[MemberNotNull(nameof(Name))]
38+
public static AlternateStreamEntry? ReadFrom(DataReader reader)
3539
{
3640
var startPos = reader.Position;
3741

3842
var length = reader.ReadInt64();
43+
3944
if (length == 0)
4045
{
4146
return null;
@@ -46,12 +51,12 @@ public static AlternateStreamEntry ReadFrom(DataReader reader)
4651
var result = new AlternateStreamEntry
4752
{
4853
Length = length,
49-
Hash = reader.ReadBytes(20)
54+
Hash = reader.ReadBytes(20).ToImmutableArray()
5055
};
5156
int nameLength = reader.ReadUInt16();
5257
if (nameLength > 0)
5358
{
54-
result.Name = Encoding.Unicode.GetString(reader.ReadBytes(nameLength + 2)).TrimEnd('\0');
59+
result.Name = reader.ReadBytes(nameLength + 2).ReadNullTerminatedUnicodeString();
5560
}
5661
else
5762
{

Library/DiscUtils.Wim/DirectoryEntry.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//
2222

2323
using System;
24+
using System.Collections.Immutable;
2425
using System.IO;
2526
using System.Text;
2627
using DiscUtils.Internal;
@@ -31,18 +32,18 @@ namespace DiscUtils.Wim;
3132

3233
internal class DirectoryEntry
3334
{
34-
public FastDictionary<AlternateStreamEntry> AlternateStreams;
35+
public FastDictionary<AlternateStreamEntry>? AlternateStreams;
3536
public FileAttributes Attributes;
3637
public long CreationTime;
37-
public string FileName;
38+
public string FileName = null!;
3839
public uint HardLink;
39-
public byte[] Hash;
40+
public ImmutableArray<byte> Hash;
4041
public long LastAccessTime;
4142
public long LastWriteTime;
4243
public long Length;
4344
public uint ReparseTag;
4445
public uint SecurityId;
45-
public string ShortName;
46+
public string? ShortName;
4647
public ushort StreamCount;
4748
public long SubdirOffset;
4849

@@ -59,7 +60,7 @@ public string SearchName
5960
}
6061
}
6162

62-
public static DirectoryEntry ReadFrom(DataReader reader)
63+
public static DirectoryEntry? ReadFrom(DataReader reader)
6364
{
6465
var startPos = reader.Position;
6566

@@ -80,7 +81,7 @@ public static DirectoryEntry ReadFrom(DataReader reader)
8081
result.CreationTime = reader.ReadInt64();
8182
result.LastAccessTime = reader.ReadInt64();
8283
result.LastWriteTime = reader.ReadInt64();
83-
result.Hash = reader.ReadBytes(20);
84+
result.Hash = reader.ReadBytes(20).ToImmutableArray();
8485
reader.Skip(4);
8586
result.ReparseTag = reader.ReadUInt32();
8687
result.HardLink = reader.ReadUInt32();
@@ -122,11 +123,11 @@ public static DirectoryEntry ReadFrom(DataReader reader)
122123

123124
// Avoid crashes on badly built WIM files with multiple streams without
124125
// a stream name
125-
if (!result.AlternateStreams.Contains(stream.Name))
126+
if (stream is not null && !result.AlternateStreams.Contains(stream.Name))
126127
{
127128
result.AlternateStreams.Add(stream);
128129

129-
if (stream.Name == "" && BufferUtilities.IsAllZeros(result.Hash))
130+
if (stream.Name == "" && BufferUtilities.IsAllZeros(result.Hash.AsSpan()))
130131
{
131132
result.Hash = stream.Hash;
132133
}
@@ -137,11 +138,11 @@ public static DirectoryEntry ReadFrom(DataReader reader)
137138
return result;
138139
}
139140

140-
public byte[] GetStreamHash(string streamName)
141+
public ImmutableArray<byte> GetStreamHash(string streamName)
141142
{
142143
if (string.IsNullOrEmpty(streamName))
143144
{
144-
if (!BufferUtilities.IsAllZeros(Hash, 0, 20))
145+
if (!BufferUtilities.IsAllZeros(Hash.AsSpan()))
145146
{
146147
return Hash;
147148
}
@@ -152,7 +153,7 @@ public byte[] GetStreamHash(string streamName)
152153
return streamEntry.Hash;
153154
}
154155

155-
return new byte[20];
156+
return default;
156157
}
157158

158159
internal long GetHeaderLength(string streamName)

Library/DiscUtils.Wim/DiscUtils.Wim.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<Description>DiscUtils WIM</Description>
4-
3+
<Description>DiscUtils WIM</Description>
54
<PackageTags>DiscUtils;WIM</PackageTags>
5+
<Nullable>enable</Nullable>
66
</PropertyGroup>
77

88
<ItemGroup>

Library/DiscUtils.Wim/FileHeader.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,37 @@
2121
//
2222

2323
using System;
24+
using System.Diagnostics.CodeAnalysis;
2425
using DiscUtils.Streams;
26+
using LTRData.Extensions.Buffers;
2527

2628
namespace DiscUtils.Wim;
2729

2830
internal class FileHeader : IByteArraySerializable
2931
{
3032
public uint BootIndex;
31-
public ShortResourceHeader BootMetaData;
33+
public ShortResourceHeader? BootMetaData;
3234
public int CompressionSize;
3335
public FileFlags Flags;
3436
public uint HeaderSize;
3537
public uint ImageCount;
36-
public ShortResourceHeader IntegrityHeader;
37-
public ShortResourceHeader OffsetTableHeader;
38+
public ShortResourceHeader? IntegrityHeader;
39+
public ShortResourceHeader OffsetTableHeader = null!;
3840
public ushort PartNumber;
39-
public string Tag;
41+
public string? Tag;
4042
public ushort TotalParts;
4143
public uint Version;
4244
public Guid WimGuid;
43-
public ShortResourceHeader XmlDataHeader;
45+
public ShortResourceHeader XmlDataHeader = null!;
4446

4547
public int Size => 512;
4648

49+
[MemberNotNull(nameof(Tag), nameof(BootMetaData), nameof(IntegrityHeader), nameof(OffsetTableHeader), nameof(XmlDataHeader))]
4750
public int ReadFrom(ReadOnlySpan<byte> buffer)
4851
{
4952
var latin1Encoding = EncodingUtilities.GetLatin1Encoding();
5053

51-
Tag = latin1Encoding.GetString(buffer.Slice(0, 8));
54+
Tag = buffer.Slice(0, 8).ReadNullTerminatedAsciiString();
5255
HeaderSize = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(8));
5356
Version = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(12));
5457
Flags = (FileFlags)EndianUtilities.ToUInt32LittleEndian(buffer.Slice(16));
@@ -77,7 +80,7 @@ public int ReadFrom(ReadOnlySpan<byte> buffer)
7780

7881
public bool IsValid()
7982
{
80-
return Tag == "MSWIM\0\0\0" && HeaderSize >= 148;
83+
return Tag == "MSWIM" && HeaderSize >= 148;
8184
}
8285

8386
void IByteArraySerializable.WriteTo(Span<byte> buffer) => throw new NotImplementedException();

Library/DiscUtils.Wim/FileResourceStream.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ internal class FileResourceStream : SparseStream.ReadOnlySparseStream
4747
private readonly int _chunkSize;
4848

4949
private int _currentChunk;
50-
private Stream _currentChunkStream;
50+
private Stream? _currentChunkStream;
5151
private readonly ShortResourceHeader _header;
5252
private readonly bool _lzxCompression;
5353
private readonly long _offsetDelta;
@@ -116,7 +116,7 @@ public override int Read(byte[] buffer, int offset, int count)
116116
var chunkOffset = (int)(_position % _chunkSize);
117117
var numToRead = Math.Min(maxToRead - totalRead, _chunkSize - chunkOffset);
118118

119-
if (_currentChunk != chunk)
119+
if (_currentChunk != chunk || _currentChunkStream is null)
120120
{
121121
_currentChunkStream = OpenChunkStream(chunk);
122122
_currentChunk = chunk;
@@ -152,7 +152,7 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
152152
var chunkOffset = (int)(_position % _chunkSize);
153153
var numToRead = Math.Min(maxToRead - totalRead, _chunkSize - chunkOffset);
154154

155-
if (_currentChunk != chunk)
155+
if (_currentChunk != chunk || _currentChunkStream is null)
156156
{
157157
_currentChunkStream = OpenChunkStream(chunk);
158158
_currentChunk = chunk;
@@ -188,7 +188,7 @@ public override int Read(Span<byte> buffer)
188188
var chunkOffset = (int)(_position % _chunkSize);
189189
var numToRead = Math.Min(maxToRead - totalRead, _chunkSize - chunkOffset);
190190

191-
if (_currentChunk != chunk)
191+
if (_currentChunk != chunk || _currentChunkStream is null)
192192
{
193193
_currentChunkStream = OpenChunkStream(chunk);
194194
_currentChunk = chunk;

Library/DiscUtils.Wim/ResourceInfo.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,29 @@
2121
//
2222

2323
using System;
24+
using System.Collections.Immutable;
25+
using System.Diagnostics.CodeAnalysis;
26+
using DiscUtils.Internal;
2427
using DiscUtils.Streams;
2528

2629
namespace DiscUtils.Wim;
2730

2831
internal class ResourceInfo
2932
{
3033
public const int Size = ShortResourceHeader.Size + 26;
31-
public byte[] Hash;
34+
public ImmutableArray<byte> Hash;
3235

33-
public ShortResourceHeader Header;
36+
public ShortResourceHeader Header = null!;
3437
public ushort PartNumber;
3538
public uint RefCount;
3639

40+
[MemberNotNull(nameof(Header))]
3741
public void Read(ReadOnlySpan<byte> buffer)
3842
{
3943
Header = new ShortResourceHeader();
4044
Header.Read(buffer);
4145
PartNumber = EndianUtilities.ToUInt16LittleEndian(buffer.Slice(ShortResourceHeader.Size));
4246
RefCount = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(ShortResourceHeader.Size + 2));
43-
Hash = buffer.Slice(ShortResourceHeader.Size + 6, 20).ToArray();
47+
Hash = buffer.Slice(ShortResourceHeader.Size + 6, 20).ToImmutableArray();
4448
}
4549
}

Library/DiscUtils.Wim/WimFile.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@
2020
// DEALINGS IN THE SOFTWARE.
2121
//
2222

23+
using DiscUtils.Internal;
24+
using DiscUtils.Streams;
2325
using System;
2426
using System.Collections.Generic;
27+
using System.Collections.Immutable;
28+
using System.Diagnostics.CodeAnalysis;
2529
using System.IO;
26-
using DiscUtils.Internal;
27-
using DiscUtils.Streams;
30+
using System.Xml.Linq;
31+
using System.Xml.XPath;
32+
using static System.Net.WebRequestMethods;
2833

2934
namespace DiscUtils.Wim;
3035

@@ -103,10 +108,45 @@ public string Manifest
103108
/// method is zero-based.</remarks>
104109
public WimFileSystem GetImage(int index)
105110
{
106-
return new WimFileSystem(this, index);
111+
var metaDataFileInfo = LocateImage(index)
112+
?? throw new ArgumentException($"No such image: {index}", nameof(index));
113+
114+
var metaDataStream = OpenResourceStream(metaDataFileInfo);
115+
116+
var volumeLabel = XDocument.Parse(Manifest)?.XPathSelectElement($"WIM/IMAGE[@INDEX=\"{index + 1}\"]/NAME")?.Value;
117+
118+
return new WimFileSystem(this, metaDataStream, volumeLabel);
119+
}
120+
121+
/// <summary>
122+
/// Gets a particular image within the file (zero-based index).
123+
/// </summary>
124+
/// <param name="index">The index of the image to retrieve.</param>
125+
/// <param name="wimFileSystem">The image as a file system.</param>
126+
/// <returns>True if index was found in the image and a file system
127+
/// was returned in <paramref name="wimFileSystem"/>, otherwise false.</returns>
128+
/// <remarks>The XML manifest file uses a one-based index, whereas this
129+
/// method is zero-based.</remarks>
130+
public bool TryGetImage(int index, [NotNullWhen(true)] out WimFileSystem? wimFileSystem)
131+
{
132+
var metaDataFileInfo = LocateImage(index);
133+
134+
if (metaDataFileInfo is null)
135+
{
136+
wimFileSystem = null;
137+
return false;
138+
}
139+
140+
var metaDataStream = OpenResourceStream(metaDataFileInfo);
141+
142+
var volumeLabel = XDocument.Parse(Manifest)?.XPathSelectElement($"WIM/IMAGE[@INDEX=\"{index + 1}\"]/NAME")?.Value;
143+
144+
wimFileSystem = new WimFileSystem(this, metaDataStream, volumeLabel);
145+
146+
return true;
107147
}
108148

109-
internal ShortResourceHeader LocateImage(int index)
149+
internal ShortResourceHeader? LocateImage(int index)
110150
{
111151
var i = 0;
112152

@@ -135,9 +175,9 @@ internal ShortResourceHeader LocateImage(int index)
135175
return null;
136176
}
137177

138-
internal ShortResourceHeader LocateResource(byte[] hash)
178+
internal ShortResourceHeader? LocateResource(ImmutableArray<byte> hash)
139179
{
140-
var hashHash = EndianUtilities.ToUInt32LittleEndian(hash, 0);
180+
var hashHash = EndianUtilities.ToUInt32LittleEndian(hash.AsSpan());
141181

142182
if (!_resources.TryGetValue(hashHash, out var headers))
143183
{
@@ -168,6 +208,7 @@ internal SparseStream OpenResourceStream(ShortResourceHeader hdr)
168208
_fileHeader.CompressionSize);
169209
}
170210

211+
[MemberNotNull(nameof(_resources))]
171212
private void ReadResourceTable()
172213
{
173214
_resources = [];
@@ -182,7 +223,7 @@ private void ReadResourceTable()
182223
var info = new ResourceInfo();
183224
info.Read(resBuffer);
184225

185-
var hashHash = EndianUtilities.ToUInt32LittleEndian(info.Hash, 0);
226+
var hashHash = EndianUtilities.ToUInt32LittleEndian(info.Hash.AsSpan());
186227

187228
if (!_resources.TryGetValue(hashHash, out var res))
188229
{

0 commit comments

Comments
 (0)