anshdadhich commited on
Commit
70d9fc3
·
verified ·
1 Parent(s): a268ccb

Upload FastSeekWpf/Core/MftReader.cs

Browse files
Files changed (1) hide show
  1. FastSeekWpf/Core/MftReader.cs +350 -0
FastSeekWpf/Core/MftReader.cs ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Buffers.Binary;
3
+ using System.Collections.Generic;
4
+ using System.IO;
5
+ using System.Runtime.InteropServices;
6
+ using FastSeekWpf.NativeInterop;
7
+
8
+ namespace FastSeekWpf.Core;
9
+
10
+ public class MftReader : IDisposable
11
+ {
12
+ private readonly IntPtr _handle;
13
+ private readonly NtfsDrive _drive;
14
+ private bool _disposed;
15
+
16
+ private const uint FallbackBuf = 4 * 1024 * 1024;
17
+ private const uint DirectBuf = 4 * 1024 * 1024;
18
+
19
+ public NtfsDrive Drive => _drive;
20
+
21
+ public MftReader(NtfsDrive drive)
22
+ {
23
+ _drive = drive;
24
+ _handle = Win32Api.CreateFileW(
25
+ drive.DevicePath,
26
+ Win32Api.GENERIC_READ,
27
+ Win32Api.FILE_SHARE_READ | Win32Api.FILE_SHARE_WRITE | Win32Api.FILE_SHARE_DELETE,
28
+ IntPtr.Zero,
29
+ Win32Api.OPEN_EXISTING,
30
+ Win32Api.FILE_FLAG_BACKUP_SEMANTICS,
31
+ IntPtr.Zero);
32
+
33
+ if (_handle == new IntPtr(-1))
34
+ throw new IOException($"Failed to open drive {drive.Letter}:");
35
+ }
36
+
37
+ public ScanResult? ScanDirect()
38
+ {
39
+ int? recordSize = ReadMftRecordSize();
40
+ if (recordSize == null) return null;
41
+
42
+ string mftPath = $"{_drive.Root}$MFT";
43
+ IntPtr mftHandle = Win32Api.CreateFileW(
44
+ mftPath,
45
+ Win32Api.GENERIC_READ,
46
+ Win32Api.FILE_SHARE_READ | Win32Api.FILE_SHARE_WRITE | Win32Api.FILE_SHARE_DELETE,
47
+ IntPtr.Zero,
48
+ Win32Api.OPEN_EXISTING,
49
+ Win32Api.FILE_FLAG_BACKUP_SEMANTICS | Win32Api.FILE_FLAG_SEQUENTIAL_SCAN,
50
+ IntPtr.Zero);
51
+
52
+ if (mftHandle == new IntPtr(-1))
53
+ return null;
54
+
55
+ try
56
+ {
57
+ var records = new List<CompactRecord>();
58
+ var nameData = new List<char>();
59
+ byte[] buffer = new byte[DirectBuf];
60
+ ulong mftIndex = 0;
61
+ int leftover = 0;
62
+
63
+ while (true)
64
+ {
65
+ bool ok = Win32Api.ReadFile(mftHandle, buffer, (uint)(buffer.Length - leftover), out uint bytesRead, IntPtr.Zero);
66
+ if (!ok || bytesRead == 0) break;
67
+
68
+ int total = leftover + (int)bytesRead;
69
+ int offset = 0;
70
+
71
+ while (offset + recordSize.Value <= total)
72
+ {
73
+ var span = new ReadOnlySpan<byte>(buffer, offset, recordSize.Value);
74
+ if (ApplyFixup(span, recordSize.Value))
75
+ {
76
+ ParseFileRecord(span, mftIndex, records, nameData);
77
+ }
78
+ mftIndex++;
79
+ offset += recordSize.Value;
80
+ }
81
+
82
+ leftover = total - offset;
83
+ if (leftover > 0)
84
+ Array.Copy(buffer, offset, buffer, 0, leftover);
85
+ }
86
+
87
+ return new ScanResult(records, nameData);
88
+ }
89
+ finally
90
+ {
91
+ Win32Api.CloseHandle(mftHandle);
92
+ }
93
+ }
94
+
95
+ public ScanResult Scan()
96
+ {
97
+ var records = new List<CompactRecord>();
98
+ var nameData = new List<char>();
99
+ byte[] buffer = new byte[FallbackBuf];
100
+
101
+ var enumData = new MFT_ENUM_DATA_V0
102
+ {
103
+ StartFileReferenceNumber = 0,
104
+ LowUsn = 0,
105
+ HighUsn = long.MaxValue
106
+ };
107
+
108
+ int enumDataSize = Marshal.SizeOf<MFT_ENUM_DATA_V0>();
109
+ IntPtr enumDataPtr = Marshal.AllocHGlobal(enumDataSize);
110
+ Marshal.StructureToPtr(enumData, enumDataPtr, false);
111
+
112
+ try
113
+ {
114
+ while (true)
115
+ {
116
+ IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
117
+ try
118
+ {
119
+ bool ok = Win32Api.DeviceIoControl(
120
+ _handle, Win32Api.FSCTL_ENUM_USN_DATA,
121
+ enumDataPtr, (uint)enumDataSize,
122
+ bufferPtr, (uint)buffer.Length,
123
+ out uint bytesReturned,
124
+ IntPtr.Zero);
125
+
126
+ if (!ok)
127
+ {
128
+ int error = Marshal.GetLastWin32Error();
129
+ if ((uint)error == 0x26) break;
130
+ break;
131
+ }
132
+
133
+ if (bytesReturned <= 8) break;
134
+
135
+ ulong nextRef = (ulong)Marshal.ReadInt64(bufferPtr);
136
+ enumData.StartFileReferenceNumber = nextRef;
137
+ Marshal.StructureToPtr(enumData, enumDataPtr, false);
138
+
139
+ int offset = 8;
140
+ while (offset + Marshal.SizeOf<USN_RECORD_V2>() <= (int)bytesReturned)
141
+ {
142
+ IntPtr recordPtr = IntPtr.Add(bufferPtr, offset);
143
+ var record = Marshal.PtrToStructure<USN_RECORD_V2>(recordPtr);
144
+
145
+ if (record.RecordLength == 0) break;
146
+
147
+ int nameOffset = offset + (int)record.FileNameOffset;
148
+ int nameLen = record.FileNameLength / 2;
149
+
150
+ var nameChars = new char[nameLen];
151
+ for (int i = 0; i < nameLen; i++)
152
+ nameChars[i] = (char)Marshal.ReadInt16(bufferPtr, nameOffset + i * 2);
153
+
154
+ int arenaOff = nameData.Count;
155
+ nameData.AddRange(nameChars);
156
+
157
+ records.Add(new CompactRecord
158
+ {
159
+ FileRef = record.FileReferenceNumber,
160
+ ParentRef = record.ParentFileReferenceNumber,
161
+ NameOff = (uint)arenaOff,
162
+ NameLen = (ushort)nameLen,
163
+ IsDir = (record.FileAttributes & 0x10) != 0
164
+ });
165
+
166
+ offset += (int)record.RecordLength;
167
+ }
168
+ }
169
+ finally
170
+ {
171
+ Marshal.FreeHGlobal(bufferPtr);
172
+ }
173
+ }
174
+ }
175
+ finally
176
+ {
177
+ Marshal.FreeHGlobal(enumDataPtr);
178
+ }
179
+
180
+ return new ScanResult(records, nameData);
181
+ }
182
+
183
+ private int? ReadMftRecordSize()
184
+ {
185
+ bool ok = Win32Api.SetFilePointerEx(_handle, 0, out _, 0);
186
+ if (!ok) return null;
187
+
188
+ byte[] boot = new byte[512];
189
+ ok = Win32Api.ReadFile(_handle, boot, 512, out uint br, IntPtr.Zero);
190
+ if (!ok || br < 512) return null;
191
+
192
+ if (boot[3] != 'N' || boot[4] != 'T' || boot[5] != 'F' || boot[6] != 'S')
193
+ return null;
194
+
195
+ ushort bytesPerSector = BinaryPrimitives.ReadUInt16LittleEndian(new ReadOnlySpan<byte>(boot, 0x0B, 2));
196
+ byte sectorsPerCluster = boot[0x0D];
197
+ int clusterSize = bytesPerSector * sectorsPerCluster;
198
+
199
+ sbyte raw = (sbyte)boot[0x40];
200
+ if (raw > 0)
201
+ return raw * clusterSize;
202
+ else
203
+ return 1 << (-raw);
204
+ }
205
+
206
+ private static bool ApplyFixup(ReadOnlySpan<byte> record, int recordSize)
207
+ {
208
+ if (record.Length < 48 || record[0] != 'F' || record[1] != 'I' || record[2] != 'L' || record[3] != 'E')
209
+ return false;
210
+
211
+ ushort fixupOff = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(4, 2));
212
+ ushort fixupCnt = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(6, 2));
213
+
214
+ if (fixupCnt < 2 || fixupOff + fixupCnt * 2 > recordSize)
215
+ return false;
216
+
217
+ byte check0 = record[fixupOff];
218
+ byte check1 = record[fixupOff + 1];
219
+
220
+ for (int i = 1; i < fixupCnt; i++)
221
+ {
222
+ int end = i * 512 - 2;
223
+ if (end + 1 >= recordSize) break;
224
+ if (record[end] != check0 || record[end + 1] != check1)
225
+ return false;
226
+ }
227
+
228
+ return true;
229
+ }
230
+
231
+ private static void ParseFileRecord(ReadOnlySpan<byte> record, ulong mftIndex,
232
+ List<CompactRecord> records, List<char> nameData)
233
+ {
234
+ ushort flags = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x16, 2));
235
+ if ((flags & 0x01) == 0) return;
236
+
237
+ bool isDir = (flags & 0x02) != 0;
238
+ ushort seq = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x10, 2));
239
+ ulong fileRef = mftIndex | ((ulong)seq << 48);
240
+
241
+ ushort firstAttr = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x14, 2));
242
+ int aoff = firstAttr;
243
+
244
+ byte bestNs = 255;
245
+ (int pos, int len, ulong parent)? bestName = null;
246
+
247
+ while (aoff + 8 <= record.Length)
248
+ {
249
+ uint atype = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff, 4));
250
+ if (atype == 0xFFFFFFFF) break;
251
+
252
+ uint alen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 4, 4));
253
+ if (alen == 0 || aoff + alen > record.Length) break;
254
+
255
+ if (atype == 0x30 && record[aoff + 8] == 0)
256
+ {
257
+ uint vlen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 16, 4));
258
+ ushort voff = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(aoff + 20, 2));
259
+ int vs = aoff + voff;
260
+
261
+ if (vs + 66 <= record.Length && vlen >= 66)
262
+ {
263
+ ulong parent = BinaryPrimitives.ReadUInt64LittleEndian(record.Slice(vs, 8));
264
+ int nlen = record[vs + 64];
265
+ byte ns = record[vs + 65];
266
+
267
+ if (vs + 66 + nlen * 2 <= record.Length)
268
+ {
269
+ if (ns == 2)
270
+ {
271
+ aoff += (int)alen;
272
+ continue;
273
+ }
274
+
275
+ byte priority = ns switch
276
+ {
277
+ 1 => 0,
278
+ 3 => 1,
279
+ 0 => 2,
280
+ _ => 3,
281
+ };
282
+
283
+ if (priority < bestNs)
284
+ {
285
+ bestNs = priority;
286
+ bestName = (vs + 66, nlen, parent);
287
+ if (priority == 0) break;
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ aoff += (int)alen;
294
+ }
295
+
296
+ if (bestName.HasValue)
297
+ {
298
+ var (namePos, nlen, parent) = bestName.Value;
299
+ int arenaOff = nameData.Count;
300
+
301
+ for (int i = 0; i < nlen; i++)
302
+ {
303
+ int p = namePos + i * 2;
304
+ nameData.Add((char)BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(p, 2)));
305
+ }
306
+
307
+ records.Add(new CompactRecord
308
+ {
309
+ FileRef = fileRef,
310
+ ParentRef = parent,
311
+ NameOff = (uint)arenaOff,
312
+ NameLen = (ushort)nlen,
313
+ IsDir = isDir
314
+ });
315
+ }
316
+ }
317
+
318
+ public void Dispose()
319
+ {
320
+ if (!_disposed)
321
+ {
322
+ Win32Api.CloseHandle(_handle);
323
+ _disposed = true;
324
+ }
325
+ GC.SuppressFinalize(this);
326
+ }
327
+
328
+ ~MftReader() => Dispose();
329
+ }
330
+
331
+ public class CompactRecord
332
+ {
333
+ public ulong FileRef { get; set; }
334
+ public ulong ParentRef { get; set; }
335
+ public uint NameOff { get; set; }
336
+ public ushort NameLen { get; set; }
337
+ public bool IsDir { get; set; }
338
+ }
339
+
340
+ public class ScanResult
341
+ {
342
+ public List<CompactRecord> Records { get; }
343
+ public List<char> NameData { get; }
344
+
345
+ public ScanResult(List<CompactRecord> records, List<char> nameData)
346
+ {
347
+ Records = records;
348
+ NameData = nameData;
349
+ }
350
+ }