Skip to content

Instantly share code, notes, and snippets.

Created August 20, 2015 16:27
Show Gist options
  • Save ryancdotorg/9f557d3513710ce91aed to your computer and use it in GitHub Desktop.
Save ryancdotorg/9f557d3513710ce91aed to your computer and use it in GitHub Desktop.
A FAT32 fragmenter, because I am a horrible person.
#!/usr/bin/env python
import random
import struct
import sys
# Most of the Fat32 class was cribbed from
def ppNum(num):
return "%s (%s)" % (hex(num), num)
class Fat32:
def __init__(self, filename):
self.fs = open(filename, 'r+b')
def getRawBytes(self, pos, numBytes):
def putRawBytes(self, pos, data):
def putUInt32(self, pos, n):
print 'write %10s@%08x' % (n, pos)
self.putRawBytes(pos, struct.pack('<i', n))
def putUInt16(self, pos, n):
self.putRawBytes(pos, struct.pack('<H', n))
def putUInt8(self, pos, n):
self.putRawBytes(pos, struct.pack('<B', n))
def getBytes(self, pos, numBytes):
byte = self.getRawBytes(pos, numBytes)
if (numBytes == 2):
formatString = "H"
elif (numBytes == 1):
formatString = "B"
elif (numBytes == 4):
formatString = "i"
raise Exception("Not implemented")
return struct.unpack("<"+formatString, byte)[0]
def getString(self, pos, numBytes):
raw = self.getRawBytes(pos, numBytes)
return struct.unpack(str(numBytes)+"s", raw)[0]
def bytesPerSector(self):
return self.getBytes(11,2)
def sectorsPerCluster(self):
return self.getBytes(13,1)
def bytesPerCluster(self):
return self.bytesPerSector * self.sectorsPerCluster
def reservedSectorCount(self):
return self.getBytes(14,2)
def numberOfFATs(self):
return self.getBytes(16,1)
def FATStart(self, numFat):
return self.reservedSectorCount * self.bytesPerSector + (self.FATSize * self.bytesPerSector * (numFat - 1))
def FATEnd(self, numFat):
return self.FATStart(numFat+1)
def FATSize(self):
return self.getBytes(36,4)
def rootStart(self):
return self.FATStart(1) + (self.FATSize * self.numberOfFATs * self.bytesPerSector)
def fsIdentityString(self):
return self.getString(82,8)
class ClEntry:
def __init__(self, offset, length, nextclidx, prevclidx=None):
self.offset = offset
self.length = length = nextclidx
self.prev = prevclidx
def __repr__(self):
return 'ClEnt(%s, %s, %s, %s)' % (self.offset, self.length,, self.prev)
class Clusters:
def __init__(self, fs):
self.fs = fs
self.fat = [] = set(), 2)
eof = self.fs.fs.tell()
for pos in range(self.fs.FATStart(1), self.fs.FATEnd(1), 4):
clidx = (pos - self.fs.FATStart(1)) / 4
# not entirely sure why -2 is required here
offset = self.fs.rootStart + (clidx-2) * self.fs.bytesPerCluster
if offset + self.fs.bytesPerCluster >= eof:
nextcluster = self.fs.getBytes(pos, 4)
self.fat.append(ClEntry(offset, self.fs.bytesPerCluster, nextcluster))
if nextcluster == 0:
for clidx in xrange(0, len(self.fat)):
cluster = self.fat[clidx]
if > 2 and < 0xffffff8:
self.fat[].prev = clidx
def setNextCluster(self, target, value):
if value is None:
value = 0
if target is not None:
writeOffset = self.fs.FATStart(1) + target * 4
self.fs.putUInt32(writeOffset, value)
self.fat[target].next = value
if value == 0:
# clear the previous cluster reference
self.fat[target].prev = None
# add cluster to the free list
if value < 0xffffff8:
# set the previous cluster reference
self.fat[value].prev = target
# remove cluster from the free list
def swap(self, clidx1, clidx2):
c1 = self.fat[clidx1]
c2 = self.fat[clidx2]
n1 =
n2 =
p1 = c1.prev
p2 = c2.prev
o1 = c1.offset
o2 = c2.offset
d1 = self.fs.getRawBytes(o1, c1.length)
d2 = self.fs.getRawBytes(o2, c2.length)
self.setNextCluster(p1, clidx2)
self.setNextCluster(clidx2, n1)
self.setNextCluster(p2, clidx1)
self.setNextCluster(clidx1, n2)
print o1
print o2
self.fs.putRawBytes(o1, d2)
self.fs.putRawBytes(o2, d1)
fs = Fat32(sys.argv[1])
print "Bytes per sector:", ppNum(fs.bytesPerSector)
print "Sectors per cluster:", ppNum(fs.sectorsPerCluster)
print "Reserved sector count:", ppNum(fs.reservedSectorCount)
print "Size of FAT (sectors):", ppNum(fs.FATSize)
print "Number of FATs:", ppNum(fs.numberOfFATs)
print "Start of FAT1:", ppNum(fs.FATStart(1))
print "Start of root directory:", ppNum(fs.rootStart)
print "Identity string:", fs.fsIdentityString
clusters = Clusters(fs)
if 0:
clusters.swap(4, 6)
for arg in sys.argv[2:]:
clidx = clusters.fat[int(arg)].next
while clidx > 2 and clidx < 0xffffff8:
free = random.sample(, 1)[0]
nextidx = clusters.fat[clidx].next
print 'swap %10s <=> %10s next %10s' % (clidx, free, nextidx)
clusters.swap(clidx, free)
clidx = nextidx
#print clusters.fat
#clusters.swap(5, 9)
#clusters.swap(6, 10)
Copy link

stuaxo commented Sep 10, 2015

Themes, that is an interesting idea .. making one that could display ascii art into the older windows or dos defragmenter would be an interesting theme ...

Copy link

spankr commented Sep 10, 2015

That is a thing of beauty!

Copy link

@FesterCluck Maximal fragmentation of a single file (It currently can't move the first cluster, though).

Copy link

No jQuery :c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment