anshdadhich commited on
Commit
e3e4995
·
verified ·
1 Parent(s): 8f85f2c

Upload FastSeekWpf/Core/MftReader.cs

Browse files
Files changed (1) hide show
  1. FastSeekWpf/Core/MftReader.cs +58 -33
FastSeekWpf/Core/MftReader.cs CHANGED
@@ -37,18 +37,22 @@ public class MftReader : IDisposable
37
  }
38
  }
39
 
40
- /// <summary>
41
- /// Primary: direct $MFT file read. Returns null if unavailable.
42
- /// Matches Rust scan_direct() exactly.
43
- /// </summary>
44
  public ScanResult? ScanDirect()
45
  {
46
  int? recordSize = ReadMftRecordSize();
47
- if (!recordSize.HasValue) return null;
 
 
 
 
48
 
49
  string mftPath = $"{_drive.Root}$MFT";
 
50
 
51
- // Open $MFT as a normal file (not the volume device)
52
  IntPtr mftHandle = Win32Api.CreateFileW(
53
  mftPath,
54
  Win32Api.GENERIC_READ,
@@ -59,7 +63,13 @@ public class MftReader : IDisposable
59
  IntPtr.Zero);
60
 
61
  if (mftHandle == new IntPtr(-1))
 
 
 
62
  return null;
 
 
 
63
 
64
  try
65
  {
@@ -68,16 +78,25 @@ public class MftReader : IDisposable
68
  byte[] buffer = new byte[DirectBuf];
69
  ulong mftIndex = 0;
70
  int leftover = 0;
 
71
 
72
  while (true)
73
  {
74
- // CRITICAL: read into buffer starting at 'leftover', not at 0
75
- // This matches Rust: ReadFile(mft_handle, &mut buffer[leftover..])
76
  bool ok = Win32Api.ReadFile(
77
  mftHandle, buffer, leftover, (uint)(buffer.Length - leftover), out uint bytesRead, IntPtr.Zero);
78
- if (!ok || bytesRead == 0)
 
 
 
 
 
 
 
 
79
  break;
 
80
 
 
81
  int total = leftover + (int)bytesRead;
82
  int offset = 0;
83
 
@@ -93,15 +112,16 @@ public class MftReader : IDisposable
93
  offset += recordSize.Value;
94
  }
95
 
96
- // Align offset down to record boundary
97
  offset = total - (total % recordSize.Value);
98
-
99
  leftover = total - offset;
100
  if (leftover > 0)
101
  Array.Copy(buffer, offset, buffer, 0, leftover);
 
 
 
102
  }
103
 
104
- Logger.Log($"[{Drive.Letter}] Direct scan: {records.Count} records from {mftIndex} positions");
105
  return new ScanResult(records, nameData);
106
  }
107
  finally
@@ -110,11 +130,14 @@ public class MftReader : IDisposable
110
  }
111
  }
112
 
113
- /// <summary>
114
- /// Fallback: FSCTL_ENUM_USN_DATA (4 MB buffer)
115
- /// </summary>
 
116
  public ScanResult Scan()
117
  {
 
 
118
  var records = new List<CompactRecord>();
119
  var nameData = new List<char>();
120
  byte[] buffer = new byte[FallbackBuf];
@@ -127,6 +150,8 @@ public class MftReader : IDisposable
127
  };
128
 
129
  int enumDataSize = Marshal.SizeOf<MFT_ENUM_DATA_V0>();
 
 
130
  IntPtr enumDataPtr = Marshal.AllocHGlobal(enumDataSize);
131
  Marshal.StructureToPtr(enumData, enumDataPtr, false);
132
 
@@ -156,17 +181,17 @@ public class MftReader : IDisposable
156
  }
157
  if (error == 122) // ERROR_INSUFFICIENT_BUFFER
158
  {
159
- Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA: buffer too small, retrying");
160
  buffer = new byte[buffer.Length * 2];
161
  continue;
162
  }
163
- Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA failed: {Win32Error(error)}");
164
  break;
165
  }
166
 
167
  if (bytesReturned <= 8)
168
  {
169
- Logger.Log($"[{Drive.Letter}] FSCTL: {bytesReturned} bytes, stopping");
170
  break;
171
  }
172
 
@@ -226,15 +251,23 @@ public class MftReader : IDisposable
226
 
227
  private int? ReadMftRecordSize()
228
  {
 
229
  Win32Api.SetFilePointerEx(_volumeHandle, 0, out _, 0);
230
 
231
  byte[] boot = new byte[512];
232
  bool ok = Win32Api.ReadFile(_volumeHandle, boot, 512, out uint br, IntPtr.Zero);
233
  if (!ok || br < 512)
 
 
 
234
  return null;
 
235
 
236
  if (boot[3] != 'N' || boot[4] != 'T' || boot[5] != 'F' || boot[6] != 'S')
 
 
237
  return null;
 
238
 
239
  ushort bytesPerSector = BinaryPrimitives.ReadUInt16LittleEndian(boot.AsSpan(0x0B, 2));
240
  byte sectorsPerCluster = boot[0x0D];
@@ -243,14 +276,10 @@ public class MftReader : IDisposable
243
  sbyte raw = (sbyte)boot[0x40];
244
  int recordSize = raw > 0 ? raw * clusterSize : 1 << (-raw);
245
 
246
- Logger.Log($"[{Drive.Letter}] Boot: bps={bytesPerSector}, spc={sectorsPerCluster}, clusterSize={clusterSize}, recordSize={recordSize}");
247
  return recordSize;
248
  }
249
 
250
- /// <summary>
251
- /// Apply NTFS multi-sector fixup. RESTORES bytes from fixup array.
252
- /// Matches Rust apply_fixup() exactly.
253
- /// </summary>
254
  private static bool ApplyFixup(byte[] record, int offset, int recordSize)
255
  {
256
  if (recordSize < 48 || record[offset] != 'F' || record[offset + 1] != 'I'
@@ -274,7 +303,6 @@ public class MftReader : IDisposable
274
  if (record[offset + end] != check0 || record[offset + end + 1] != check1)
275
  return false;
276
 
277
- // RESTORE the real bytes from the fixup array
278
  record[offset + end] = record[offset + fixupOff + i * 2];
279
  record[offset + end + 1] = record[offset + fixupOff + i * 2 + 1];
280
  }
@@ -282,9 +310,6 @@ public class MftReader : IDisposable
282
  return true;
283
  }
284
 
285
- /// <summary>
286
- /// Parse one MFT FILE record. Matches Rust parse_file_record() exactly.
287
- /// </summary>
288
  private static void ParseFileRecord(
289
  ReadOnlySpan<byte> record,
290
  ulong mftIndex,
@@ -292,7 +317,7 @@ public class MftReader : IDisposable
292
  List<char> nameData)
293
  {
294
  ushort flags = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x16, 2));
295
- if ((flags & 0x01) == 0) return; // Record not in use
296
 
297
  bool isDir = (flags & 0x02) != 0;
298
  ushort seq = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x10, 2));
@@ -312,7 +337,7 @@ public class MftReader : IDisposable
312
  uint alen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 4, 4));
313
  if (alen == 0 || aoff + alen > record.Length) break;
314
 
315
- if (atype == 0x30 && record[aoff + 8] == 0) // $FILE_NAME, resident
316
  {
317
  uint vlen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 16, 4));
318
  ushort voff = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(aoff + 20, 2));
@@ -326,7 +351,7 @@ public class MftReader : IDisposable
326
 
327
  if (vs + 66 + nlen * 2 <= record.Length)
328
  {
329
- if (ns == 2) // DOS 8.3 name — skip
330
  {
331
  aoff += (int)alen;
332
  continue;
@@ -334,9 +359,9 @@ public class MftReader : IDisposable
334
 
335
  byte priority = ns switch
336
  {
337
- 1 => 0, // Win32
338
- 3 => 1, // Win32 + DOS
339
- 0 => 2, // POSIX
340
  _ => 3,
341
  };
342
 
 
37
  }
38
  }
39
 
40
+ // ------------------------------------------------------------------
41
+ // Primary: direct $MFT file read
42
+ // ------------------------------------------------------------------
43
+
44
  public ScanResult? ScanDirect()
45
  {
46
  int? recordSize = ReadMftRecordSize();
47
+ if (!recordSize.HasValue)
48
+ {
49
+ Logger.Log($"[{Drive.Letter}] ReadMftRecordSize failed — cannot do direct scan");
50
+ return null;
51
+ }
52
 
53
  string mftPath = $"{_drive.Root}$MFT";
54
+ Logger.Log($"[{Drive.Letter}] Opening $MFT file: {mftPath}");
55
 
 
56
  IntPtr mftHandle = Win32Api.CreateFileW(
57
  mftPath,
58
  Win32Api.GENERIC_READ,
 
63
  IntPtr.Zero);
64
 
65
  if (mftHandle == new IntPtr(-1))
66
+ {
67
+ int err = Marshal.GetLastWin32Error();
68
+ Logger.Log($"[{Drive.Letter}] CreateFileW({mftPath}) failed: {Win32Error(err)} (code={err})");
69
  return null;
70
+ }
71
+
72
+ Logger.Log($"[{Drive.Letter}] $MFT opened OK, handle=0x{mftHandle:X}");
73
 
74
  try
75
  {
 
78
  byte[] buffer = new byte[DirectBuf];
79
  ulong mftIndex = 0;
80
  int leftover = 0;
81
+ long totalRead = 0;
82
 
83
  while (true)
84
  {
 
 
85
  bool ok = Win32Api.ReadFile(
86
  mftHandle, buffer, leftover, (uint)(buffer.Length - leftover), out uint bytesRead, IntPtr.Zero);
87
+ if (!ok)
88
+ {
89
+ int err = Marshal.GetLastWin32Error();
90
+ Logger.Log($"[{Drive.Letter}] ReadFile failed at offset {totalRead}: {Win32Error(err)} (code={err})");
91
+ break;
92
+ }
93
+ if (bytesRead == 0)
94
+ {
95
+ Logger.Log($"[{Drive.Letter}] ReadFile returned 0 bytes at offset {totalRead} — EOF");
96
  break;
97
+ }
98
 
99
+ totalRead += bytesRead;
100
  int total = leftover + (int)bytesRead;
101
  int offset = 0;
102
 
 
112
  offset += recordSize.Value;
113
  }
114
 
 
115
  offset = total - (total % recordSize.Value);
 
116
  leftover = total - offset;
117
  if (leftover > 0)
118
  Array.Copy(buffer, offset, buffer, 0, leftover);
119
+
120
+ if (mftIndex % 50000 == 0)
121
+ Logger.Log($"[{Drive.Letter}] Progress: {mftIndex:N0} records parsed, {records.Count:N0} valid");
122
  }
123
 
124
+ Logger.Log($"[{Drive.Letter}] Direct scan: {records.Count} records from {mftIndex} positions, {totalRead:N0} bytes read");
125
  return new ScanResult(records, nameData);
126
  }
127
  finally
 
130
  }
131
  }
132
 
133
+ // ------------------------------------------------------------------
134
+ // Fallback: FSCTL_ENUM_USN_DATA
135
+ // ------------------------------------------------------------------
136
+
137
  public ScanResult Scan()
138
  {
139
+ Logger.Log($"[{Drive.Letter}] Starting FSCTL_ENUM_USN_DATA fallback...");
140
+
141
  var records = new List<CompactRecord>();
142
  var nameData = new List<char>();
143
  byte[] buffer = new byte[FallbackBuf];
 
150
  };
151
 
152
  int enumDataSize = Marshal.SizeOf<MFT_ENUM_DATA_V0>();
153
+ Logger.Log($"[{Drive.Letter}] MFT_ENUM_DATA_V0 size = {enumDataSize} bytes");
154
+
155
  IntPtr enumDataPtr = Marshal.AllocHGlobal(enumDataSize);
156
  Marshal.StructureToPtr(enumData, enumDataPtr, false);
157
 
 
181
  }
182
  if (error == 122) // ERROR_INSUFFICIENT_BUFFER
183
  {
184
+ Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA: buffer too small ({buffer.Length}), retrying with larger buffer");
185
  buffer = new byte[buffer.Length * 2];
186
  continue;
187
  }
188
+ Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA failed (iter {iterations}): {Win32Error(error)} (code={error})");
189
  break;
190
  }
191
 
192
  if (bytesReturned <= 8)
193
  {
194
+ Logger.Log($"[{Drive.Letter}] FSCTL: {bytesReturned} bytes returned, stopping");
195
  break;
196
  }
197
 
 
251
 
252
  private int? ReadMftRecordSize()
253
  {
254
+ Logger.Log($"[{Drive.Letter}] Reading boot sector from volume...");
255
  Win32Api.SetFilePointerEx(_volumeHandle, 0, out _, 0);
256
 
257
  byte[] boot = new byte[512];
258
  bool ok = Win32Api.ReadFile(_volumeHandle, boot, 512, out uint br, IntPtr.Zero);
259
  if (!ok || br < 512)
260
+ {
261
+ int err = Marshal.GetLastWin32Error();
262
+ Logger.Log($"[{Drive.Letter}] Boot sector read failed: {Win32Error(err)} (code={err})");
263
  return null;
264
+ }
265
 
266
  if (boot[3] != 'N' || boot[4] != 'T' || boot[5] != 'F' || boot[6] != 'S')
267
+ {
268
+ Logger.Log($"[{Drive.Letter}] Not NTFS (missing signature at offset 3)");
269
  return null;
270
+ }
271
 
272
  ushort bytesPerSector = BinaryPrimitives.ReadUInt16LittleEndian(boot.AsSpan(0x0B, 2));
273
  byte sectorsPerCluster = boot[0x0D];
 
276
  sbyte raw = (sbyte)boot[0x40];
277
  int recordSize = raw > 0 ? raw * clusterSize : 1 << (-raw);
278
 
279
+ Logger.Log($"[{Drive.Letter}] Boot OK: bps={bytesPerSector}, spc={sectorsPerCluster}, clusterSize={clusterSize}, recordSize={recordSize}");
280
  return recordSize;
281
  }
282
 
 
 
 
 
283
  private static bool ApplyFixup(byte[] record, int offset, int recordSize)
284
  {
285
  if (recordSize < 48 || record[offset] != 'F' || record[offset + 1] != 'I'
 
303
  if (record[offset + end] != check0 || record[offset + end + 1] != check1)
304
  return false;
305
 
 
306
  record[offset + end] = record[offset + fixupOff + i * 2];
307
  record[offset + end + 1] = record[offset + fixupOff + i * 2 + 1];
308
  }
 
310
  return true;
311
  }
312
 
 
 
 
313
  private static void ParseFileRecord(
314
  ReadOnlySpan<byte> record,
315
  ulong mftIndex,
 
317
  List<char> nameData)
318
  {
319
  ushort flags = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x16, 2));
320
+ if ((flags & 0x01) == 0) return;
321
 
322
  bool isDir = (flags & 0x02) != 0;
323
  ushort seq = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(0x10, 2));
 
337
  uint alen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 4, 4));
338
  if (alen == 0 || aoff + alen > record.Length) break;
339
 
340
+ if (atype == 0x30 && record[aoff + 8] == 0)
341
  {
342
  uint vlen = BinaryPrimitives.ReadUInt32LittleEndian(record.Slice(aoff + 16, 4));
343
  ushort voff = BinaryPrimitives.ReadUInt16LittleEndian(record.Slice(aoff + 20, 2));
 
351
 
352
  if (vs + 66 + nlen * 2 <= record.Length)
353
  {
354
+ if (ns == 2)
355
  {
356
  aoff += (int)alen;
357
  continue;
 
359
 
360
  byte priority = ns switch
361
  {
362
+ 1 => 0,
363
+ 3 => 1,
364
+ 0 => 2,
365
  _ => 3,
366
  };
367