forked from Ninoh-FOX/RG350-ROGUE-CFW
-
Notifications
You must be signed in to change notification settings - Fork 0
/
trimfat.py
218 lines (183 loc) · 6.1 KB
/
trimfat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python2
from os import SEEK_SET, SEEK_END
def checkPowerOfTwo(n, description):
if n == 0 or (n & (n - 1)) != 0:
raise ValueError('%s (%d) is not a power of two'
% (description.capitalize(), n))
return n
def formatSize(size):
'''Returns a human-readable representation of a size in bytes.
'''
size = float(size)
suffixes = iter(('bytes', 'KiB', 'MiB', 'GiB', None))
while True:
suffix = next(suffixes)
if suffix is None or size < 1024:
break
size /= 1024
if size >= 100:
return '%.0f %s' % (size, suffix)
elif size >= 10:
return '%.1f %s' % (size, suffix)
else:
return '%.2f %s' % (size, suffix)
def formatPowerOfTwoSize(size):
'''Returns a human-readable representation of a size in bytes,
where the size is guaranteed to be a power of two.
'''
suffixes = iter(('bytes', 'KiB', 'MiB', 'GiB', None))
while True:
suffix = next(suffixes)
if suffix is None or size < 1024:
break
assert size % 1024 == 0, size
size /= 1024
return '%d %s' % (size, suffix)
def checkPropertyIsPowerOfTwo(prop, description):
if prop.fget is None:
newGet = None
else:
def newGet(obj):
return checkPowerOfTwo(prop.fget(obj), description)
if prop.fset is None:
newSet = None
else:
def newSet(obj, value):
checkPowerOfTwo(value, description)
prop.fset(obj, value)
return property(newGet, newSet, prop.fdel)
def get8(data, offset):
return ord(data[offset])
def get16(data, offset):
return ord(data[offset]) | (ord(data[offset + 1]) << 8)
def get32(data, offset):
return ord(data[offset]) | (ord(data[offset + 1]) << 8) \
| (ord(data[offset + 2]) << 16) | (ord(data[offset + 3]) << 24)
def set8(data, offset, value):
if not (0 <= value < 0x100):
raise ValueError('Value does not fit in 8 bits: %d' % value)
data[offset] = chr(value)
def set16(data, offset, value):
if not (0 <= value < 0x10000):
raise ValueError('Value does not fit in 16 bits: %d' % value)
data[offset] = chr(value & 0xFF)
data[offset + 1] = chr((value >> 8) & 0xFF)
def set32(data, offset, value):
if not (0 <= value < 0x100000000):
raise ValueError('Value does not fit in 32 bits: %d' % value)
data[offset] = chr(value & 0xFF)
data[offset + 1] = chr((value >> 8) & 0xFF)
data[offset + 2] = chr((value >> 16) & 0xFF)
data[offset + 3] = chr((value >> 24) & 0xFF)
def property8(offset):
return property(
lambda self: get8(self.data, offset),
lambda self, value: set8(self.data, offset, value)
)
def property16(offset):
return property(
lambda self: get16(self.data, offset),
lambda self, value: set16(self.data, offset, value)
)
def property32(offset):
return property(
lambda self: get32(self.data, offset),
lambda self, value: set32(self.data, offset, value)
)
class BootSector(object):
sectorSize = checkPropertyIsPowerOfTwo(
property16(0x00B), 'sector size')
sectorsPerCluster = checkPropertyIsPowerOfTwo(
property8(0x00D), 'sectors per cluster')
reservedSectors = property16(0x00E)
numFATs = property8(0x010)
sectorsPerFAT = property32(0x024)
@classmethod
def read(cls, f):
f.seek(0, SEEK_SET)
data = f.read(512)
# A few sanity checks: is this really a FAT32 partition?
if len(data) != 512:
raise ValueError('Too little data to even contain a boot sector')
if data[-2 : ] != '\x55\xaa':
raise ValueError('Missing boot sector signature')
if data[0x052 : 0x05A] != 'FAT32 ':
raise ValueError('Missing FAT32 file system type string')
return cls(data)
def __init__(self, data):
self.data = data
def getFATRange(self, index):
'''Returns the offset and length (both in bytes) of the given FAT.
'''
assert 0 <= index < self.numFATs, index
sectorSize = self.sectorSize
length = self.sectorsPerFAT * sectorSize
offset = self.reservedSectors * sectorSize + index * length
return offset, length
class FAT(object):
@classmethod
def read(cls, f, bootSector, index):
offset, length = bootSector.getFATRange(index)
f.seek(offset, SEEK_SET)
data = f.read(length)
if len(data) != length:
raise ValueError('Data ends within FAT%d' % index)
return cls(data)
def __init__(self, data):
assert len(data) % 4 == 0, len(data)
self.data = data
def __getitem__(self, index):
return get32(self.data, index * 4) & 0x0fffffff
def __setitem__(self, index, value):
set32(self.data, index * 4, value)
def __len__(self):
return len(self.data) / 4
def dump(self):
for i in xrange(len(self.data) / 4):
print '%08X' % self[i],
if i % 8 == 7:
print
def getOffsetLastSector(imageFile):
# Determine size of container (partition/image).
# Note: stat-based approaches don't work for device nodes.
imageFile.seek(0, SEEK_END)
imageSize = imageFile.tell()
print 'Image size:', formatSize(imageSize)
# Read and check boot sector.
bootSector = BootSector.read(imageFile)
sectorSize = bootSector.sectorSize
imageSectors, imageRemainder = divmod(imageSize, sectorSize)
if imageRemainder != 0:
raise ValueError('Partition/image size (%d) is not a multiple of '
'sector size (%d)' % (imageSize, sectorSize))
numFATs = bootSector.numFATs
last = 2
for idx in xrange(0, numFATs):
fat = FAT.read(imageFile, bootSector, idx)
for cluster in xrange(2, len(fat)):
val = fat[cluster]
if val >= 2 and (val <= 0x0FFFFFEF or val >= 0x0FFFFFF8) and cluster > last:
last = cluster
print 'Highest cluster found: %i' % last
lastSector = bootSector.reservedSectors \
+ bootSector.sectorsPerFAT * numFATs \
+ (last + 1) * bootSector.sectorsPerCluster
print 'Image can be trimmed from sector %i (one sector is %s)' % \
(lastSector, formatSize(sectorSize))
newSize = lastSector * sectorSize
print 'New image file size will be %s (saves %s)' % \
(formatSize(newSize), formatSize(imageSize - newSize))
return lastSector * sectorSize
if __name__ == '__main__':
import sys
if len(sys.argv) == 2:
with open(sys.argv[1], 'a+b') as imageFile:
length = getOffsetLastSector(imageFile)
print 'Truncating %s...' % sys.argv[1]
imageFile.truncate(length)
else:
print >>sys.stderr, 'Usage: trimfat.py <image>'
print >>sys.stderr, ''
print >>sys.stderr, 'Trims the given filesystem image.'
print >>sys.stderr, 'Only FAT32 is supported.'
sys.exit(2)