Skip to content

Commit 5bf5d9a

Browse files
committed
Small fix
- redundant Record dwMFTRecNumber is parsed only with NTFS 3.1 - added a proper Record iterator
1 parent 0c88fc9 commit 5bf5d9a

File tree

6 files changed

+32
-16
lines changed

6 files changed

+32
-16
lines changed

FATtools/NTFS/Attribute.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def __str__ (self): return utils.class2str(self, "$ATTRIBUTE_LIST Item @%x\n" %
164164

165165

166166
class File_Name(Attribute):
167+
# Always resident
167168
specific_layout = {
168169
0x00: ('u64FileReference', '<Q'),
169170
0x08: ('u64CTime', '<Q'),
@@ -180,7 +181,7 @@ class File_Name(Attribute):
180181
def __init__(self, parent, offset):
181182
Attribute.__init__(self, parent, offset)
182183
common_update_and_swap(self)
183-
# Always resident - so name is at (24+66) bytes from start
184+
# Name is at (24+66) bytes from start
184185
i = self._i+90
185186
self.FileName = (self._buf[i:i+self.ucbFileName*2]).decode('utf-16le')
186187

@@ -316,7 +317,7 @@ class Volume_Information(Attribute):
316317
# Always resident
317318
specific_layout = {
318319
0x00: ('u64Reserved', '<Q'),
319-
0x08: ('bMajorVersion', 'B'), # 1.1=NT 3.51, 1.2=NT4, 3.0=2K, 3.1=XP+
320+
0x08: ('bMajorVersion', 'B'), # 1.1/1.2=NT 3.5/4, 3.0=2K, 3.1=XP+
320321
0x09: ('bMinorVersion', 'B'),
321322
0x0A: ('wFlags', '<H'), # 1=dirty, 2=log resize, 4=to upgrade, 8=mounted on NT4, 10h=del USN, 20h=repair OIDS, 8000h=modified by CHKDSK
322323
0x0C: ('dwReserved', '<I') } # 0x10 (16) bytes
@@ -331,6 +332,7 @@ def is_dirty(self):
331332
return self.wFlags & 1
332333

333334
class Security_Descriptor(Attribute):
335+
# At least since Windows NT 3.51
334336
specific_layout = {}
335337

336338
def __init__(self, parent, offset):

FATtools/NTFS/Boot.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ class Bootsector:
77
0x03: ('chOemID', '8s'), # "NTFS "
88
0x0B: ('wBytesPerSec', '<H'), # typically, 512
99
0x0D: ('uchSecPerClust', 'B'), # typically, 8 (4K cluster)
10-
0x0E: ('u64Unused1', '<Q'), # 0xf800000000000000
10+
0x0E: ('wReservedSectors', '<H'), # unused
11+
0x10: ('sUnused1', '3s'), # unused
12+
0x13: ('wUnused1', '<H'), # unused
1113
0x15: ('uchMediaDescriptor', 'B'), # typically 0xF8 (HDD)
1214
0x16: ('wUnused2', '<H'),
13-
0x18: ('wSecPerTrack', '<H'),
14-
0x1A: ('wNumberOfHeads', '<H'),
15-
0x1C: ('u64Unused3', '<Q'),
16-
0x24: ('dwUnknown', '<I'), # typically 0x800080
17-
0x25: ('dwUnused4', '<I'),
15+
0x18: ('wSecPerTrack', '<H'), # typically, 0x3F
16+
0x1A: ('wNumberOfHeads', '<H'), # typically, 0xFF
17+
0x1C: ('dwHiddenSectors', '<I'), # typically, 0x3F
18+
0x20: ('dwUnused1', '<I'), # typically 0
19+
0x24: ('dwUnused2', '<I'),# typically 0x800080
1820
0x28: ('u64TotalSectors', '<Q'), # count of volume sectors
1921
0x30: ('u64MFTLogicalClustNum', '<Q'), # cluster of the $MFT Record (and Master File Table start)
2022
0x38: ('u64MFTMirrLogicalClustNum', '<Q'), # $MFTMirr cluster (backup of first 4 $MFT Record)
2123
0x40: ('nClustPerMFTRecord', '<b'), # if n<0 then 2^-n. Typically, 0xF6 (-10) or 1K.
22-
0x44: ('nClustPerIndexRecord', '<b'),
24+
0x44: ('nClustPerIndexRecord', '<b'), # if n<0 then 2^-n.
2325
0x48: ('u64VolumeSerialNum', '<Q'),
2426
0x50: ('dwChecksum', '<I'), # typically zero
2527
0x54: ('chBootstrapCode', '426s'),

FATtools/NTFS/DatarunStream.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ class DatarunStream:
77
"Accesses a sequence of data runs as a continuous stream"
88
def __init__ (self, boot, dataruns, size):
99
self.boot = boot
10-
self._runs = dataruns
10+
self._runs = dataruns # [(length, base_absolute_offset), (length, base_offset_delta), ...]
1111
self.curdatarun = 0 # datarun paired with current stream offset
1212
self.curdatarunpos = 0 # offset in current datarun
1313
self.size = size # virtual stream length
1414
self.seekpos = 0 # virtual stream offset
1515
self.IsValid = 1 # to make FATtools code happy
16+
self.seek(0)
1617

1718
def close(self):
1819
pass
@@ -66,6 +67,7 @@ def seek(self, offset, whence=0):
6667
else:
6768
break
6869
# Having found the datarun where the final position lays, we seek from its offset
69-
if DEBUG&8: log("seek @%x, datarun=%d, relativepos=%x", self.seekpos, i-2, todo)
70+
if DEBUG&8: log("seek @%x, datarun=%d, base=0x%x, offset=0x%x", self.seekpos, i-2, self._runs[i+1], todo)
71+
#~ print("seek @%x, datarun=%d, relpos=%x, absbase=%x"%(self.seekpos, i-2, todo, self._runs[i+1]))
7072
# BUG? Real disk stream has to seek with an Index, not the datarun stream?
7173
self.boot.stream.seek(self._runs[i+1] + todo)

FATtools/NTFS/Record.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class Record:
2222
0x1C: ('dwAllLength', '<I'),
2323
0x20: ('u64BaseMftRec', '<Q'),
2424
0x28: ('wNextAttrID', '<H'),
25-
0x2A: ('wFixupPattern', '<H'),
26-
0x2C: ('dwMFTRecNumber', '<I') # NTFS v3.0+
25+
0x2A: ('wFixupPattern', '<H')
2726
} # Size = 0x2C (44 byte)
2827

2928
def __init__ (self, boot, mftstream):
@@ -42,8 +41,11 @@ def __init__ (self, boot, mftstream):
4241
self._vk = {} # { name: offset}
4342
for k, v in self._kv.items():
4443
self._vk[v[0]] = k
45-
44+
4645
if not self.wFlags & 0x1: return # Unused record
46+
47+
if self.wUSAOffset == 0x30: # NTFS v3.1
48+
self.layout[0x2C] = ('dwMFTRecNumber', '<I') # redundant Record number
4749

4850
if self.fileSignature != b'FILE':
4951
print("Malformed NTFS Record @%x!" % self._pos)
@@ -99,6 +101,14 @@ def __init__ (self, boot, mftstream):
99101

100102
def __str__ (self): return utils.class2str(self, "MFT Record 0x%X @%x\n" % (self._pos//self.boot.cbRecord, self._pos))
101103

104+
def iterator(self):
105+
"Iterates through all MFT Records, from start"
106+
offset = 0
107+
while offset < self._stream.size:
108+
self._stream.seek(offset)
109+
yield Record(self.boot, self._stream)
110+
offset += self.boot.cbRecord
111+
102112
def next(self, index=1):
103113
"Parses the next or n-th Record"
104114
off = index * self.boot.cbRecord

FATtools/NTFS/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
COPYRIGHT = '''Copyright (C)2012-2025, by maxpat78. GNU GPL v3 applies.
55
This free software reads NTFS file systems WITH ABSOLUTELY NO WARRANTY!'''
66

7-
VERSION = '0.9.4b'
7+
VERSION = '0.9.5b'

FATtools/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.1.4'
1+
__version__ = '1.1.5'

0 commit comments

Comments
 (0)