Don't like ads? PRO users don't see any ads ;-)
Guest

subfdisk v1.1

By: a guest on Mar 14th, 2010  |  syntax: Python  |  size: 28.02 KB  |  hits: 136  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. #!/usr/bin/python
  2.  
  3. import sys, os, struct, array, time
  4.  
  5. HEXFILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in xrange(256)])
  6.  
  7. def getbyte(d):
  8.         return struct.unpack("<B",d)[0]
  9. def getword(d):
  10.         return struct.unpack("<H",d)[0]
  11. def getdword(d):
  12.         return struct.unpack("<I",d)[0]
  13.  
  14. def dump(src, length=16):
  15.         N=0; result=''
  16.         while src:
  17.                 s,src = src[:length],src[length:]
  18.                 hexa = ' '.join(["%02X"%ord(x) for x in s])
  19.                 s = s.translate(HEXFILTER)
  20.                 result += "%04X:  %-*s  %s\n" % (N, length*3, hexa, s)
  21.                 N+=length
  22.         return result
  23.  
  24. VERSION = "0.95"
  25.  
  26. class BlockDevice:
  27.         def __init__(self, file):
  28.                 self.fd = open(file,"rb+")
  29.                 self.written = {}
  30.                 self.sectorsize = 512
  31.         def read(self, sector):
  32.                 if sector in self.written:
  33.                         return self.written[sector]
  34.                 else:
  35.                         self.fd.seek(sector*self.sectorsize)
  36.                         data = self.fd.read(self.sectorsize)
  37.                         return data
  38.         def mread(self, start, length):
  39.                 self.fd.seek(start*self.sectorsize)
  40.                 data = self.fd.read(self.sectorsize*length)
  41.                 for i in xrange(length):
  42.                         if (i+start) in self.written:
  43.                                 data = data[:i*self.sectorsize] + self.written[i+start] + data[i*self.sectorsize+self.sectorsize:]
  44.                 return data
  45.         def write(self, sector, data):
  46.                 if len(data) != self.sectorsize:
  47.                         raise ValueError("Data size must equal sector size (%d, should be %d)"%(data,self.sectorsize))
  48.                 old = self.read(sector)
  49.                 if old == data:
  50.                         return
  51.                 self.written[sector] = data
  52.         def commit(self):
  53.                 count = 0
  54.                 for sector,data in self.written.items():
  55.                         self.fd.seek(sector*self.sectorsize)
  56.                         self.fd.write(data)
  57.                         count += 1
  58.                 self.written = {}
  59.                 return count
  60.         def size(self):
  61.                 self.fd.seek(0,2)
  62.                 size = self.fd.tell()
  63.                 if size%self.sectorsize != 0:
  64.                         raise RuntimeError("Device size not divisible by sector size!")
  65.                 return self.fd.tell()/self.sectorsize
  66.         def revert(self):
  67.                 self.written = {}
  68.         def dumpchanges(self):
  69.                 items = self.written.items()
  70.                 items.sort(lambda x,y: cmp(x[0],y[0]))
  71.                 for sector,data in items:
  72.                         print "== SECTOR 0x%08X ==========================================================================="%sector
  73.                         print dump(data)
  74.  
  75. class Partition:
  76.         def __init__(self, blockdevice, start, length, name, description=""):
  77.                 self.blockdevice = blockdevice
  78.                 self.start = start
  79.                 self.length = length
  80.                 self.name = name
  81.                 self.description = description
  82.         def read(self, sector):
  83.                 if sector >= self.length:
  84.                         raise ValueError("Attempted to read data beyond partition end")
  85.                 else:
  86.                         return self.blockdevice.read(sector+self.start)
  87.         def mread(self, sector, length):
  88.                 if (sector+length) > self.length:
  89.                         raise ValueError("Attempted to read data beyond partition end")
  90.                 else:
  91. #                       d = ""
  92.                         return self.blockdevice.mread(sector+self.start,length)
  93. #                       for i in xrange(length):
  94. #                               d += self.blockdevice.read(sector+self.start+i)
  95. #                       return d
  96.         def write(self, sector, data):
  97.                 if sector >= self.length:
  98.                         raise ValueError("Attempted to write data beyond partition end")
  99.                 else:
  100.                         self.blockdevice.write(sector+self.start, data)
  101.         def mwrite(self, sector, data):
  102.                 if len(data)%self.blockdevice.sectorsize != 0:
  103.                         raise ValueError("Data size must be a multiple of the sector size")
  104.                 else:
  105.                         dsecs = len(data)/self.blockdevice.sectorsize
  106.                         if (sector+dsecs) > self.length:
  107.                                 raise ValueError("Attempted to write data beyond partition end")
  108.                         else:
  109.                                 for i in xrange(dsecs):
  110.                                         self.blockdevice.write(sector+i+self.start, data[i*self.blockdevice.sectorsize:(i+1)*self.blockdevice.sectorsize])
  111.                
  112.         def __str__(self):
  113.                 return "[%s] %d - %d (%d sectors, %.1fMB): %s"%(self.name,self.start,self.start+self.length-1,self.length,self.length/2048.0,self.description)
  114.  
  115. class PartitionCollection:
  116.         def __init__(self, blockdevice):
  117.                 self.blockdevice = blockdevice
  118.                 self.managers = []
  119.                 self.partitions = []
  120.         def register(self, partitionmanager):
  121.                 self.managers.append(partitionmanager)
  122.                 self.partitions += partitionmanager.gettable()
  123.         def refresh(self):
  124.                 self.partitions = []
  125.                 for i in self.managers:
  126.                         self.partitions += i.gettable()
  127.         def __getitem__(self, item):
  128.                 return self.partitions.__getitem__(item)
  129.         def __len__(self):
  130.                 return self.partitions.__len__()
  131.         def __str__(self):
  132.                 s = "Partition Table:\n"
  133.                 for i,part in enumerate(self.partitions):
  134.                         s += "  %d: %s\n"%(i+1,str(part))
  135.                 return s
  136.  
  137. class MBRPartitionTable:
  138.         def __init__(self, device, prefix="MBR-", sector=0):
  139.                 self.sector = sector
  140.                 self.prefix = prefix
  141.                 self.device = device
  142.         def readentry(self, num):
  143.                 if num >= 4:
  144.                         raise ValueError("MBR Partition index out of bounds")
  145.                 offset = num * 16 + 0x1be
  146.                 entry = self.device.read(self.sector)[offset:offset+16]
  147.                 boot,sh,scs1,scs2,ptype,eh,ecs1,ecs2,start,size = struct.unpack("<BBBBBBBBII",entry)
  148.                 schs = (scs2 + ((scs1 & 0xc0) << 2),sh,scs1&0x3F)
  149.                 echs = (ecs2 + ((ecs1 & 0xc0) << 2),eh,ecs1&0x3F)
  150.                 return (boot&0x80==0x80),ptype,start,size,schs,echs
  151.         def writeentry(self, num, boot, ptype, start, size):
  152.                 if num >= 4:
  153.                         raise ValueError("MBR Partition index out of bounds")
  154.                 offset = num * 16 + 0x1be
  155.                 if boot:
  156.                         bent = 0x80
  157.                 else:
  158.                         bent = 0x00
  159.                 entry = struct.pack("<BBBBBBBBII",bent,0xff,0xff,0xff,ptype,0xff,0xff,0xff,start,size)
  160.                 sector = self.device.read(self.sector)
  161.                 sector = sector[:offset] + entry + sector[offset+16:]
  162.                 self.device.write(self.sector,sector)
  163.         def hasentry(self, num):
  164.                 if num >= 4:
  165.                         raise ValueError("MBR Partition index out of bounds")
  166.                 offset = num * 16 + 0x1be
  167.                 entry = self.device.read(self.sector)[offset:offset+16]
  168.                 boot,sh,scs1,scs2,ptype,eh,ecs1,ecs2,start,size = struct.unpack("<BBBBBBBBII",entry)
  169.                 return ptype != 0x00
  170.         def gettable(self):
  171.                 table = []
  172.                 for i in xrange(4):
  173.                         bootable,ptype,start,size,schs,echs = self.readentry(i)
  174.                         if ptype != 0x00:
  175.                                 table.append(Partition(self.device,start,size,self.prefix+"%d"%(i+1),"MBR Partition of type 0x%02x"%ptype))
  176.                 return table
  177.  
  178. class NonFatalFilesystemError(RuntimeError):
  179.         pass
  180.  
  181. class XBOXPartitionTable:
  182.         def __init__(self, device, useFG=False, prefix="XBOX-"):
  183.                 self.prefix = prefix
  184.                 self.device = device
  185.                 self.useFG = useFG
  186.                 self.devicesize = self.device.size()
  187.                 if self.devicesize < 0x00EE8AB0:
  188.                         raise RuntimeError("Device too small to be an XBOX Hard Disk: %d sectors"%self.devicesizee)
  189.                 sector = self.device.read(3)
  190.                 sig = sector[0:4]
  191.                 print dump(sig)
  192.                 if sig!=0x42524652:
  193.                         print "This XBOX Hard Disk signature is missing."
  194.                         yesno = raw_input("Do you want to mark it? (Yes/No) (You need to type 'Yes'): ")
  195.                         if yesno == "Yes":
  196.                                 sig2 = struct.pack("<4s","BRFR")
  197.                                 sector = sig2 + sector[4:]
  198.                                 print dump(sector)
  199.                                 offset = 3
  200.                                 self.device.write(offset,sector)
  201.                                 self.device.commit
  202.                 else:
  203.                         raise RuntimeError("Invalid XBOX Config Area signature: '%s'"%repr(sig))
  204.         def gettable(self):
  205.                 table = []
  206.                 table.append(Partition(self.device,0x00000000,0x00000003-0x00000000,self.prefix+"BOOT","XBOX Legacy Boot Area (MBR)"))
  207.                 table.append(Partition(self.device,0x00000003,0x00000400-0x00000003,self.prefix+"CONFIG","XBOX Configuration Area"))
  208.                 table.append(Partition(self.device,0x00000400,0x00177400-0x00000400,self.prefix+"X","XBOX Game Cache 1"))
  209.                 table.append(Partition(self.device,0x00177400,0x002EE400-0x00177400,self.prefix+"Y","XBOX Game Cache 2"))
  210.                 table.append(Partition(self.device,0x002EE400,0x00465400-0x002EE400,self.prefix+"Z","XBOX Game Cache 3"))
  211.                 table.append(Partition(self.device,0x00465400,0x0055F400-0x00465400,self.prefix+"C","XBOX System"))
  212.                 table.append(Partition(self.device,0x0055F400,0x00EE8AB0-0x0055F400,self.prefix+"E","XBOX Data"))
  213.                
  214.                 if self.devicesize > 0x00EE8AB0:
  215.                         if self.devicesize <= 0x10000000:
  216.                                 table.append(Partition(self.device,0x00EE8AB0,self.devicesize-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
  217.                         else:
  218.                                 if self.useFG:
  219.                                         table.append(Partition(self.device,0x00EE8AB0,0x10000000-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
  220.                                         table.append(Partition(self.device,0x10000000,self.devicesize-0x10000000,self.prefix+"G","XBOX LBA48 Extended"))
  221.                                 else:
  222.                                         table.append(Partition(self.device,0x00EE8AB0,self.devicesize-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
  223.                        
  224.                
  225.                 return table
  226.  
  227. class Filesystem:
  228.         def __init__(self, partition):
  229.                 self.partition = partition
  230.  
  231.  
  232. class FATX(Filesystem):
  233.         def __init__(self, partition):
  234.                 Filesystem.__init__(self, partition)
  235.                 self.bootblock = partition.read(0)
  236.                 self.justFormatted = False
  237.                 print dump(self.bootblock[0:4])
  238.                 if self.bootblock[0:4] != 0x46415458:
  239.                        
  240.                         print "This FATX filesystem is not formatted."
  241.                         yesno = raw_input("Do you want to format it? (Yes/No) (You need to type 'Yes'): ")
  242.                         if yesno == "Yes":
  243.                                 print "Writing header..."
  244.                                 import random
  245.                                 size = self.partition.length*self.partition.blockdevice.sectorsize
  246.                                 clustersize=0x20
  247.                                 self.bootblock = struct.pack("<4sIIHI","FATX",random.randint(0,2**32-1),clustersize,1,0)
  248.                                 self.bootblock += "\xff"*0xFEE
  249.                                 self.partition.mwrite(0,self.bootblock)
  250.                                 print "Writing FAT..."
  251.                                 self.readfsinfo()
  252.                                 self.showinfo()
  253.                                 self.fat = array.array(self.arraycode,((self.dataclusters+1))*[self.fat_unused])
  254.                                 self.fat[0] = self.fat_eoc
  255.                                 self.fat[1] = self.fat_eoc_set # let's do things the MS way...
  256.                                 fatstring = self.fat.tostring()
  257.                                 fatstring += ((self.fatsize*512)-len(fatstring))*"\xff"
  258.                                 self.partition.mwrite(8,fatstring)
  259.                                 print "Writing Root Directory..."
  260.                                 self.putcluster(self.rootdir,"\xff"*self.clustersize*512)
  261.                                 print "Done!"
  262.                                 self.justFormatted = True
  263.                                 return
  264.                                        
  265.                         else:
  266.                                 raise NonFatalFilesystemError("Invalid FATX signature")
  267.                
  268.                 self.readfsinfo()
  269.        
  270.         def readfsinfo(self):
  271.                 self.volumeid, self.clustersize, self.fatcopies = struct.unpack("IIH",self.bootblock[4:14])
  272.                 if self.fatcopies != 1:
  273.                         raise NonFatalFilesystemError("FATX has %d FAT copies, only 1 supported"%self.fatcopies)
  274.                 self.numclusters = self.partition.length / self.clustersize
  275.                 if self.numclusters >= 0xfff4:
  276.                         self.fatbits = 32
  277.                         self.arraycode = "I"
  278.                         self.fat_unused = 0x00000000L
  279.                         self.fat_eoc = 0xfffffff8L
  280.                         self.fat_eoc_set = 0xffffffffL
  281.                         self.fat_reserved = 0xfffffff0L
  282.                         self.fat_bad = 0xfffffff7L
  283.                 else:
  284.                         self.fatbits = 16
  285.                         self.fat_unused = 0x0000
  286.                         self.fat_eoc = 0xfff8
  287.                         self.fat_eoc_set = 0xffff
  288.                         self.fat_reserved = 0xfff0
  289.                         self.fat_bad = 0xfff7
  290.                         self.arraycode = "H"
  291.                
  292.                 self.fatsize = self.numclusters * self.fatbits/8
  293.                 if self.fatsize % 4096 != 0:
  294.                         self.fatsize = ((self.fatsize / 4096) + 1) * 4096
  295.                 self.fatsize /= 512
  296.                
  297.                 self.dataoffset = self.fatsize + 8 - self.clustersize
  298.                 self.rootdir = 1
  299.                 self.dataclusters = (self.partition.length - self.fatsize - 8)/self.clustersize
  300.                
  301.                 #print dump(self.partition.mread(8,2))
  302.        
  303.         def getcluster(self, num):
  304.                 if num < 1:
  305.                         raise ValueError("Invalid FAT cluster number %d"%(num))
  306.                 return self.partition.mread(self.dataoffset+(num)*self.clustersize, self.clustersize)
  307.        
  308.         def putcluster(self, num, data):
  309.                 if num < 1:
  310.                         raise ValueError("Invalid FAT cluster number %d"%(num))
  311.                 if len(data) != 512*self.clustersize:
  312.                         raise ValueError("Invalid FAT cluster size %d"%(num))
  313.                 self.partition.mwrite(self.dataoffset+(num)*self.clustersize, data)
  314.                
  315.         def showinfo(self):
  316.                 print "  FATX volume on %s: %.1fMB raw size"%(self.partition.name,self.partition.length/2048.0)
  317.                 print "  Volume ID: %08X"%self.volumeid
  318.                 print "  Cluster size: %d sectors (%dKB)"%(self.clustersize,self.clustersize/2)
  319.                 print "  FAT type: %d bits"%(self.fatbits)
  320.                 print "  FAT copies: %d"%(self.fatcopies)
  321.                 print "  FAT size: %d sectors (%dKB)"%(self.fatsize,self.fatsize/2)
  322.                 print "  Total clusters: %d (%d data clusters)"%(self.numclusters,self.dataclusters)
  323.  
  324.         def readchain(self, cluster):
  325.                 data = ""
  326.                 while cluster < self.fat_eoc:
  327.                         #print "Reading chain, cluster = %d"%cluster
  328.                         d = self.getcluster(cluster)
  329.                         data += d
  330.                         #print "Cluster length: %d"%len(d)
  331.                         cluster = self.fat[cluster]
  332.                 #print "Chain length: %d"%len(data)
  333.                 return data
  334.  
  335.         def writechain(self, cluster, data):
  336.                 while cluster < self.fat_eoc:
  337.                         self.putcluster(cluster, data[:self.clustersize*512])
  338.                         data = data[self.clustersize*512:]
  339.                         cluster = self.fat[cluster]
  340.                 if data != "":
  341.                         raise RuntimeError("Chain length mismatch when writing chain!")
  342.  
  343.         def extendchain(self, cluster):
  344.                 #print "Extending cluster chain %d..."%cluster
  345.                 if cluster == self.rootdir:
  346.                         raise RuntimeError("Root directory is full and cannot be extended! (Or so I hear, it may very well be possible)")
  347.                 while self.fat[cluster] < self.fat_eoc:
  348.                         cluster = self.fat[cluster]
  349.                 #print "- Start %d"%cluster
  350.                 for newcluster in xrange(len(self.fat)):
  351.                         if self.fat[newcluster] == self.fat_unused:
  352.                                 print "- New %d"%newcluster
  353.                                 self.fat[newcluster] = self.fat_eoc_set
  354.                                 self.fat[cluster] = newcluster
  355.                                 return
  356.                 else:
  357.                         raise RuntimeError("No free clusters available")
  358.  
  359.         def finddir(self, path):
  360.                 cd = self.rootdir
  361.                 path = path.replace("\\","/")
  362.                
  363.                 for element in path.split("/"):
  364.                         if element == "":
  365.                                 continue
  366.                         dirdata = self.readchain(cd)
  367.                         for i in xrange(len(dirdata)/64):
  368.                                 namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
  369.                                 if namesz == 0xff or namesz == 0x00:
  370.                                         return RuntimeError("Path element %s not found"%element)
  371.                                 if namesz > 42:
  372.                                         continue
  373.                                 name = name[:namesz]
  374.                                 if name.lower() == element.lower():
  375.                                         cd = cluster
  376.                                         break
  377.                         else:
  378.                                 raise RuntimeError("Path element %s not found"%element)
  379.                 return cd
  380.  
  381.         def addfile(self, dircluster, filename, start, length):
  382.                 dirdata = self.readchain(dircluster)
  383.                 #print "SDir: %d"%len(dirdata)
  384.                 dirdataextension = 0
  385.  
  386.                 if len(filename) > 42:
  387.                         raise RuntimeError("Filename length exceeds maximum of 42: %s (%d)"%(filename,len(filename)))
  388.                 pfilename = filename+"\xFF"*(42-len(filename))
  389.                 # first check for existing file
  390.                 for i in xrange(len(dirdata)/64):
  391.                         namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
  392.                         if namesz == 0xff or namesz == 0:
  393.                                 break
  394.                         if namesz > 42:
  395.                                 continue
  396.                         name = name[:namesz]
  397.                         if name.lower() == filename.lower():
  398.                                 raise RuntimeError("File %s already exists!"%filename)
  399.                 # Now store the file
  400.                 for i in xrange(len(dirdata)/64):
  401.                         namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
  402.                         if namesz == 0xff or namesz == 0x00:
  403.                                 dirdata = dirdata[:i*64] + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + dirdata[i*64+64:]
  404.                                 #print "A Dir: %d"%len(dirdata)
  405.                                 if i == ((len(dirdata)/64) - 1):
  406.                                         dirdata += "\xFF"*(self.clustersize * 512)
  407.                                         dirdataextension += 1
  408.                                         #print "End-extending dir: %d %d"%(len(dirdata),dirdataextension)
  409.                                 dirdata = dirdata[:i*64+64] + 64*"\xFF" + dirdata[i*64+128:]
  410.                                 #print "B Dir: %d"%len(dirdata)
  411.                                 break
  412.                         if namesz > 42:
  413.                                 dirdata = dirdata[:i*64] + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + dirdata[i*64+64:]
  414.                                 break
  415.                 else:
  416.                         dirdataextension += 1
  417.                         dirdata = dirdata + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + \
  418.                                 "\xFF"*(self.clustersize * 512 - 64)
  419.                         #print "Extending dir: %d %d"%(len(dirdata),dirdataextension)
  420.  
  421.                 for i in xrange(dirdataextension):
  422.                         self.extendchain(dircluster)
  423.                 #print "EDir: %d"%len(dirdata)
  424.                 self.writechain(dircluster,dirdata)
  425.  
  426.         def reserve_area(self):
  427.                
  428.                 print "Reading FAT...",
  429.                 sys.stdout.flush()
  430.                
  431.                 start = 8
  432.                 sleft = self.fatsize - 8
  433.                 left = self.fatsize - 8
  434.                
  435.                 np = 0.1
  436.                
  437.                 fstr = ""
  438.                
  439.                 self.fat = array.array(self.arraycode)
  440.                
  441.                 while left > 0:
  442.                         qty = min(left,1024)
  443.                         s = self.partition.mread(start,qty)
  444.                         self.fat.fromstring(s)
  445.                         if self.fatsize > 20000 and np < (1-(float(left)/sleft)):
  446.                                 print ("%d%%"%int((np+0.001)*100)),
  447.                                 sys.stdout.flush()
  448.                                 np += 0.1
  449.                         start += qty
  450.                         left -= qty
  451.                
  452.                 print "Done."
  453.                 print "Analyzing free space...",
  454.                 sys.stdout.flush()
  455.                
  456.                 self.freegroups = []
  457.                 self.totalfree = 0
  458.                 lastfree = False
  459.                 freestart = 0
  460.                 np = 0.1
  461.                
  462.                 for cluster,value in enumerate(self.fat):
  463.                         if cluster % 4096 == 0:
  464.                                 if np < cluster/float(self.numclusters):
  465.                                         print ("%d%%"%int((np+0.001)*100)),
  466.                                         sys.stdout.flush()
  467.                                         np += 0.1
  468.  
  469.                         if value == self.fat_unused:
  470.                                 self.totalfree += 1
  471.                                 if not lastfree:
  472.                                         freestart = cluster
  473.                                         lastfree = True
  474.                         elif lastfree:
  475.                                         self.freegroups.append((freestart, cluster-freestart))
  476.                                         lastfree = False
  477.                 if lastfree:
  478.                         self.freegroups.append((freestart, cluster+1-freestart))
  479.                
  480.                 print "Done."
  481.                
  482.                 print "%d clusters free (%.1fMB)"%(self.totalfree, (self.totalfree*self.clustersize)/2048.0)
  483.                
  484.                 if self.totalfree == 0:
  485.                         print "No space available!"
  486.                         return False
  487.                
  488.                 self.freegroups.sort(lambda x,y: cmp(x[1],y[1]))
  489.                 maxblock = self.freegroups[-1][1]
  490.                 print "Largest contiguous block available: %d clusters (%.1fMB)"%(maxblock,maxblock*self.clustersize/2048.0)
  491.                
  492.                 havevalue = False
  493.                
  494.                 while True:
  495.                         if not havevalue:
  496.                                 value = raw_input("Target partition size (#{K,M,G}): ")
  497.                         havevalue = False
  498.                         mult = 1
  499.                         if not value:
  500.                                 continue
  501.                         if value[-1] == "B":
  502.                                 value = value[:-1]
  503.                         if not value:
  504.                                 print "Invalid size value."
  505.                                 continue
  506.                         if value[-1] in "KMG":
  507.                                 if value[-1] == "K":
  508.                                         mult = 1024
  509.                                 elif value[-1] == "M":
  510.                                         mult = 1024*1024
  511.                                 elif value[-1] == "G":
  512.                                         mult = 1024*1024*1024
  513.                                 value=value[:-1]
  514.                         if not value:
  515.                                 print "Invalid size value."
  516.                                 continue
  517.                         try:
  518.                                 numbytes = int(value)
  519.                         except ValueError:
  520.                                 print "Invalid size value."
  521.                                 continue
  522.                         numbytes *= mult
  523.                         if numbytes % 512 != 0:
  524.                                 numsectors = numbytes / 512 + 1
  525.                                 numbytes = numsectors * 512
  526.                         else:
  527.                                 numsectors = numbytes / 512
  528.                         if numsectors % self.clustersize != 0:
  529.                                 numclusters = numsectors / self.clustersize + 1
  530.                         else:
  531.                                 numclusters = numsectors / self.clustersize
  532.                        
  533.                         if numclusters > maxblock:
  534.                                 print "Size is too large"
  535.                                 continue
  536.                         print "You requested %d sectors (%.1fMB)"%(numsectors, numsectors/2048.0)
  537.                        
  538.                         nextlarger = len(self.freegroups)-1
  539.                         nextsmaller = 0
  540.                         if numbytes < self.freegroups[nextsmaller][1]*self.clustersize*512:
  541.                                 nextsmaller = None
  542.                         else:
  543.                                 while numbytes >= self.freegroups[nextsmaller][1]*self.clustersize*512:
  544.                                         nextsmaller += 1
  545.                                 nextsmaller -= 1
  546.                         while numbytes <= self.freegroups[nextlarger][1]*self.clustersize*512:
  547.                                 nextlarger -= 1
  548.                                 if nextlarger == -1:
  549.                                         break
  550.                         nextlarger += 1
  551.                        
  552.                         print "Closest contiguous block larger than the requested size: %d clusters (%.1fMB)"%\
  553.                                 (self.freegroups[nextlarger][1],self.freegroups[nextlarger][1]*self.clustersize/2048.0)
  554.                         if nextsmaller is not None:    
  555.                                 print "Closest contiguous block smaller than the requested size: %d clusters (%.1fMB)"%\
  556.                                         (self.freegroups[nextsmaller][1],self.freegroups[nextsmaller][1]*self.clustersize/2048.0)
  557.                        
  558.                         print "- Enter L to use the size of the closest larger contiguous block"
  559.                         if nextsmaller is not None:    
  560.                                 print "- Enter S to use the size of the closest smaller contiguous block"
  561.                         print "- Enter T to use the specified target size"
  562.                         if numsectors % self.clustersize != 0:
  563.                                 print "- Enter R to use the specified target size, rounded to the next cluster (this is free)"
  564.                         value = raw_input("- Or enter a new target size value (#{K,M,G}): ")
  565.                        
  566.                         if value in "lL":
  567.                                 block = self.freegroups[nextlarger]
  568.                         elif value in "sS":
  569.                                 block = self.freegroups[nextsmaller]
  570.                         elif value in "tTrR":
  571.                                 block = self.freegroups[nextlarger][0],numclusters
  572.                                 if value in "rR":
  573.                                         numsectors = numclusters * self.clustersize
  574.                         else:
  575.                                 havevalue = True
  576.                                 continue
  577.                        
  578.                         if (self.totalfree-block[1])*self.clustersize < 2048:
  579.                                 print
  580.                                 print "You should probably leave some free space. Who knows what will happen otherwise."
  581.                                 print "Try again."
  582.                                 continue
  583.                        
  584.                         kpercluster = self.clustersize / 2
  585.                        
  586.                         allocbytes = block[1] * self.clustersize * 512
  587.                         if numbytes >= 2**31-1:
  588.                                 print
  589.                                 print "The requested size will not fit into a standard file in FATX."
  590.                                 print "Please choose among the following options:"
  591.                                 print " A) Use multiple 1GB files. This is safe."
  592.                                 print " B) Use multiple 2GB-%dK files. This is safe, but not very neat."%kpercluster
  593.                                 print " C) Use multiple 2GB files. This will show up as -2GB using the MS kernel."
  594.                                 print " D) Use multiple 4GB-%dK files. This will show up as -%dK using the MS kernel."%(kpercluster,kpercluster)
  595.                                 print " E) Choose a new partition size."
  596.                                 choice = raw_input("Your choice: ")
  597.                                 while choice not in "ABCDEabcde":
  598.                                         print "Invalid choice %s"%choice
  599.                                         choice = raw_input("Your choice: ")
  600.                                 if choice in "eE":
  601.                                         continue
  602.                                 elif choice in "aA":
  603.                                         chunksize = 2048*1024/self.clustersize
  604.                                 elif choice in "bB":
  605.                                         chunksize = (4096*1024/self.clustersize)-1
  606.                                 elif choice in "cC":
  607.                                         chunksize = 4096*1024/self.clustersize
  608.                                 elif choice in "dD":
  609.                                         chunksize = (8096*1024/self.clustersize)-1
  610.                                        
  611.                                 chunksrequired = numbytes / (chunksize*self.clustersize*512)
  612.                                 if numbytes % (chunksize*self.clustersize*512) != 0:
  613.                                         chunksrequired += 1
  614.                                 print
  615.                                 print "%d file(s) will be created."%chunksrequired
  616.                                
  617.                                 filetab = []
  618.                                
  619.                                 for filenum in xrange(chunksrequired):
  620.                                         start = filenum*chunksize + block[0]
  621.                                         length = min(chunksize, block[1]-filenum*chunksize)
  622.                                         bytelength = min(chunksize*self.clustersize,numsectors-(filenum*chunksize*self.clustersize))*512
  623.                                        
  624.                                         print "Allocating %d clusters (%d - %d) for file %d (%.1fMB)..."%\
  625.                                                 (length, start, start+length-1, filenum, length*self.clustersize/2048.0)
  626.                                                
  627.                                         filetab.append((start, bytelength))
  628.                                        
  629.                                         for cluster in xrange(start, start+length):
  630.                                                 if cluster == (start+length-1):
  631.                                                         self.fat[cluster] = self.fat_eoc_set
  632.                                                 else:
  633.                                                         self.fat[cluster] = cluster+1
  634.                                
  635.                                 print
  636.                                 path = raw_input("Enter the path where the files will be stored: ")
  637.  
  638.                                 dircluster = self.finddir(path)
  639.                                
  640.                                 base = raw_input("Enter the basename for the files: ")
  641.  
  642.                                 print "Writing %d files..."%chunksrequired
  643.                                
  644.                                 for file,entry in enumerate(filetab):
  645.                                         filename = base + ".%03d"%file
  646.                                         print " %s"%filename
  647.                                         start, length = entry
  648.                                         self.addfile(dircluster,filename,start,length)
  649.                                
  650.                         else:
  651.                                 print "Allocating %d clusters (%d - %d, %.1fMB)..."%(numclusters, block[0],block[0]+block[1]-1,allocbytes/1024.0/1024.0)
  652.                                 for cluster in xrange(block[0], block[0]+block[1]):
  653.                                         if cluster == (block[0]+block[1]):
  654.                                                 self.fat[cluster] = self.fat_eoc_set
  655.                                         else:
  656.                                                 self.fat[cluster] = cluster+1
  657.                                                
  658.                                 print
  659.                                 path = raw_input("Enter the path where the file will be stored: ")
  660.                                 dircluster = self.finddir(path)
  661.                                 filename = raw_input("Enter the filename: ")
  662.                                 print "Writing file %s..."%filename    
  663.                                 self.addfile(dircluster,filename,block[0],numbytes)
  664.                        
  665.                         print "Storing FAT back..."
  666.                        
  667.                         fatstring = self.fat.tostring()
  668.                        
  669.                         self.fat = None
  670.  
  671.                         fatstring += ((self.fatsize*512)-len(fatstring))*"\xff"
  672.                        
  673.                         self.partition.mwrite(8,fatstring)
  674.                        
  675.                         print "Done!"
  676.                        
  677.                         return (block[0]*self.clustersize+self.dataoffset, block[1]*self.clustersize)
  678.  
  679. def main(argv):
  680.        
  681.         print "subfdisk "+VERSION+" by Hector Martin <hector@marcansoft.com>"
  682.        
  683.         if len(argv) < 2 or len(argv) > 3:
  684.                 print "Usage: python %s [-g] <device>"%argv[0]
  685.                 print "<device> should be the entire hard disk device, not a partition."
  686.                 print "-g: use split F/G partitions for HDDs larger than 128GiB/137GB"
  687.                 return
  688.        
  689.         useFG = False
  690.        
  691.         if argv[1] == "-g":
  692.                 useFG = True
  693.                 argv=argv[0:1]+argv[2:]
  694.        
  695.         print
  696.         device = BlockDevice(argv[1])
  697.         print "%s: %d bytes (%.1fMB)"%(argv[1],device.size()*device.sectorsize,device.size()*device.sectorsize/1024.0/1024.0)
  698.         print
  699.        
  700.         print "Reading partitions..."
  701.        
  702.         while True:
  703.                
  704.                 partitions = PartitionCollection(device)
  705.                 partitions.register(XBOXPartitionTable(device,useFG))
  706.                 partitions.register(MBRPartitionTable(device))
  707.                 print str(partitions)
  708.                
  709.                 while True:
  710.                         part = raw_input("Choose the partition you want to work with: ")
  711.                         if not part: continue
  712.                         if part == "q": return
  713.                         try:
  714.                                 numpart = int(part)-1
  715.                         except ValueError:
  716.                                 for numpart,partition in enumerate(partitions):
  717.                                         if partition.name == part:
  718.                                                 break
  719.                                 else:
  720.                                         print "Invalid partition name/number: %s"%part
  721.                                         continue
  722.                         if numpart >= len(partitions) or numpart <= 2:
  723.                                 print "Invalid partition name/number: %s"%part
  724.                                 continue
  725.                         break
  726.                
  727.                 print
  728.                 print "Partition %d chosen (%s)"%(numpart+1,partitions[numpart].name)
  729.                 print "Loading filesystem...",
  730.                 try:
  731.                         fs = FATX(partitions[numpart])
  732.                         if not fs.justFormatted:
  733.                                        
  734.                                 print
  735.                                 print "Filesystem Information:"
  736.                                 fs.showinfo()
  737.                                 print
  738.                                 area = fs.reserve_area()
  739.                                 if not area:
  740.                                         continue
  741.                                 start,length = area
  742.                                 start += partitions[numpart].start
  743.                                 print "MBR Partitions:"
  744.                                 mbrpt = MBRPartitionTable(device)
  745.                                 parts = mbrpt.gettable()
  746.                                 if len(parts) == 0:
  747.                                         print "  [No MBR Partitions]"
  748.                                 else:
  749.                                         for i in mbrpt.gettable():
  750.                                                 print "  "+str(i)
  751.                                
  752.                                 while True:
  753.                                         pnum = raw_input("Enter the partition number which you want to create: ")
  754.                                         try:
  755.                                                 pnum = int(pnum)
  756.                                                 if pnum > 4 or pnum < 1:
  757.                                                         raise ValueError("blah")
  758.                                                 break
  759.                                         except ValueError:
  760.                                                 print "Invalid value."
  761.                                                 continue
  762.                                        
  763.                                         if mbrpt.hasentry(pnum-1):
  764.                                                 yesno = raw_input("Partition table entry %d already exists! Do you want to overwrite it? (Y/N): ")
  765.                                                 if yesno in "Yy":
  766.                                                         break
  767.                                         else:
  768.                                                 break
  769.                                
  770.                                 while True:
  771.                                         ptype = raw_input("Enter the type for the partition (82 for Linux swap, 83 for Linux, etc): ")
  772.                                         try:
  773.                                                 ptype = int(ptype,16)
  774.                                                 if ptype == 0:
  775.                                                         raise ValueError("blah")
  776.                                                 break
  777.                                         except ValueError:
  778.                                                 print "Invalid value."
  779.                                                 continue
  780.                                
  781.                                 mbrpt.writeentry(pnum-1,0x00,ptype,start,length)
  782.                                
  783.                                 print "Done!"
  784.                                 print
  785.                                
  786.                                 partitions = PartitionCollection(device)
  787.                                 partitions.register(XBOXPartitionTable(device))
  788.                                 partitions.register(MBRPartitionTable(device))
  789.                                 print str(partitions)
  790.                                
  791.                 except NonFatalFilesystemError:
  792.                         t,v,tb = sys.exc_info()
  793.                         print v
  794.                         print
  795.                         print "Press enter to continue"
  796.                         raw_input()
  797.                         continue
  798.                        
  799.                 while True:
  800.                         yesno = raw_input("Do you want to perform other operations? (Y/N): ")
  801.                         if yesno in "YyNn":
  802.                                 break
  803.                 if yesno in "Yy":
  804.                         continue
  805.                 else:
  806.                         #device.dumpchanges()
  807.                         yesno = raw_input("Commit changes? WARNING: THIS OPERATION IS NOT REVERSIBLE (Yes/No) (You need to type 'Yes'): ")
  808.                         if yesno == "Yes":
  809.                                 print "COMMITTING CHANGES!"
  810.                                 device.commit()
  811.                                 print "Done."
  812.                                 print
  813.                                 print "REMEMBER TO RUN hdparm -z %s OR REBOOT LINUX TO REREAD THE PARTITION TABLE!"%argv[1]
  814.                         break
  815.                        
  816.  
  817. if __name__=="__main__":
  818.         try:
  819.                 main(sys.argv)
  820.         except MemoryError:
  821.                 print
  822.                 print "Out of Memory."
  823.                 print "You probably tried to format or work with a large partition from an Xbox LiveCD."
  824.                 print "As you probably already know, the Xbox contains only 64MB of RAM, part of which"
  825.                 print "is required to run the LiveCD. subfdisk needs to keep all changes in RAM for"
  826.                 print "safety, but the FAT of large partitions can easily be very large, exceeding all"
  827.                 print "available RAM."
  828.                 print
  829.                 print "Suggestion: enable some swapspace. One good place to do that is the game cache"
  830.                 print "FATX partitions (X/Y/Z). If you have just created the BRFR signature, Linux"
  831.                 print "will not know about these partitions yet. Try `hdparm -z /dev/hda' or reboot your"
  832.                 print "Xbox. Once you have done that, run `mkswap /dev/hda53 && swapon /dev/hda53' to"
  833.                 print "enable some swap. Once you are done, you can disable swap again and reformat"
  834.                 print "/dev/hda53 as FATX again, using mkfs.fatx or this program (just pick the"
  835.                 print "XBOX-Y partition and you will be prompted). Make sure you only do this after"
  836.                 print "disabling swap with `swapoff /dev/hda53'!"