#!/usr/bin/python

import sys, os, struct, array, time

HEXFILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in xrange(256)])

def getbyte(d):
	return struct.unpack("<B",d)[0]
def getword(d):
	return struct.unpack("<H",d)[0]
def getdword(d):
	return struct.unpack("<I",d)[0]

def dump(src, length=16):
	N=0; result=''
	while src:
		s,src = src[:length],src[length:]
		hexa = ' '.join(["%02X"%ord(x) for x in s])
		s = s.translate(HEXFILTER)
		result += "%04X:  %-*s  %s\n" % (N, length*3, hexa, s)
		N+=length
	return result

VERSION = "0.95"

class BlockDevice:
	def __init__(self, file):
		self.fd = open(file,"rb+")
		self.written = {}
		self.sectorsize = 512
	def read(self, sector):
		if sector in self.written:
			return self.written[sector]
		else:
			self.fd.seek(sector*self.sectorsize)
			data = self.fd.read(self.sectorsize)
			return data
	def mread(self, start, length):
		self.fd.seek(start*self.sectorsize)
		data = self.fd.read(self.sectorsize*length)
		for i in xrange(length):
			if (i+start) in self.written:
				data = data[:i*self.sectorsize] + self.written[i+start] + data[i*self.sectorsize+self.sectorsize:]
		return data
	def write(self, sector, data):
		if len(data) != self.sectorsize:
			raise ValueError("Data size must equal sector size (%d, should be %d)"%(data,self.sectorsize))
		old = self.read(sector)
		if old == data:
			return
		self.written[sector] = data
	def commit(self):
		count = 0
		for sector,data in self.written.items():
			self.fd.seek(sector*self.sectorsize)
			self.fd.write(data)
			count += 1
		self.written = {}
		return count
	def size(self):
		self.fd.seek(0,2)
		size = self.fd.tell()
		if size%self.sectorsize != 0:
			raise RuntimeError("Device size not divisible by sector size!")
		return self.fd.tell()/self.sectorsize
	def revert(self):
		self.written = {}
	def dumpchanges(self):
		items = self.written.items()
		items.sort(lambda x,y: cmp(x[0],y[0]))
		for sector,data in items:
			print "== SECTOR 0x%08X ==========================================================================="%sector
			print dump(data)

class Partition:
	def __init__(self, blockdevice, start, length, name, description=""):
		self.blockdevice = blockdevice
		self.start = start
		self.length = length
		self.name = name
		self.description = description
	def read(self, sector):
		if sector >= self.length:
			raise ValueError("Attempted to read data beyond partition end")
		else:
			return self.blockdevice.read(sector+self.start)
	def mread(self, sector, length):
		if (sector+length) > self.length:
			raise ValueError("Attempted to read data beyond partition end")
		else:
#			d = ""
			return self.blockdevice.mread(sector+self.start,length)
#			for i in xrange(length):
#				d += self.blockdevice.read(sector+self.start+i)
#			return d
	def write(self, sector, data):
		if sector >= self.length:
			raise ValueError("Attempted to write data beyond partition end")
		else:
			self.blockdevice.write(sector+self.start, data)
	def mwrite(self, sector, data):
		if len(data)%self.blockdevice.sectorsize != 0:
			raise ValueError("Data size must be a multiple of the sector size")
		else:
			dsecs = len(data)/self.blockdevice.sectorsize
			if (sector+dsecs) > self.length:
				raise ValueError("Attempted to write data beyond partition end")
			else:
				for i in xrange(dsecs):
					self.blockdevice.write(sector+i+self.start, data[i*self.blockdevice.sectorsize:(i+1)*self.blockdevice.sectorsize])
		
	def __str__(self):
		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)

class PartitionCollection:
	def __init__(self, blockdevice):
		self.blockdevice = blockdevice
		self.managers = []
		self.partitions = []
	def register(self, partitionmanager):
		self.managers.append(partitionmanager)
		self.partitions += partitionmanager.gettable()
	def refresh(self):
		self.partitions = []
		for i in self.managers:
			self.partitions += i.gettable()
	def __getitem__(self, item):
		return self.partitions.__getitem__(item)
	def __len__(self):
		return self.partitions.__len__()
	def __str__(self):
		s = "Partition Table:\n"
		for i,part in enumerate(self.partitions):
			s += "  %d: %s\n"%(i+1,str(part))
		return s

class MBRPartitionTable:
	def __init__(self, device, prefix="MBR-", sector=0):
		self.sector = sector
		self.prefix = prefix
		self.device = device
		sector = self.device.read(self.sector)
		sig = getword(sector[0x1FE:0x200])
		if sig != 0xaa55:
			raise RuntimeError("Invalid MBR Partition Table signature: 0x%04X"%sig)
	def readentry(self, num):
		if num >= 4:
			raise ValueError("MBR Partition index out of bounds")
		offset = num * 16 + 0x1be
		entry = self.device.read(self.sector)[offset:offset+16]
		boot,sh,scs1,scs2,ptype,eh,ecs1,ecs2,start,size = struct.unpack("<BBBBBBBBII",entry)
		schs = (scs2 + ((scs1 & 0xc0) << 2),sh,scs1&0x3F)
		echs = (ecs2 + ((ecs1 & 0xc0) << 2),eh,ecs1&0x3F)
		return (boot&0x80==0x80),ptype,start,size,schs,echs
	def writeentry(self, num, boot, ptype, start, size):
		if num >= 4:
			raise ValueError("MBR Partition index out of bounds")
		offset = num * 16 + 0x1be
		if boot:
			bent = 0x80
		else:
			bent = 0x00
		entry = struct.pack("<BBBBBBBBII",bent,0xff,0xff,0xff,ptype,0xff,0xff,0xff,start,size)
		sector = self.device.read(self.sector)
		sector = sector[:offset] + entry + sector[offset+16:]
		self.device.write(self.sector,sector)
	def hasentry(self, num):
		if num >= 4:
			raise ValueError("MBR Partition index out of bounds")
		offset = num * 16 + 0x1be
		entry = self.device.read(self.sector)[offset:offset+16]
		boot,sh,scs1,scs2,ptype,eh,ecs1,ecs2,start,size = struct.unpack("<BBBBBBBBII",entry)
		return ptype != 0x00
	def gettable(self):
		table = []
		for i in xrange(4):
			bootable,ptype,start,size,schs,echs = self.readentry(i)
			if ptype != 0x00:
				table.append(Partition(self.device,start,size,self.prefix+"%d"%(i+1),"MBR Partition of type 0x%02x"%ptype))
		return table

class NonFatalFilesystemError(RuntimeError):
	pass

class XBOXPartitionTable:
	def __init__(self, device, useFG=False, prefix="XBOX-"):
		self.prefix = prefix
		self.device = device
		self.useFG = useFG
		self.devicesize = self.device.size()
		if self.devicesize < 0x00EE8AB0:
			raise RuntimeError("Device too small to be an XBOX Hard Disk: %d sectors"%self.devicesizee)
		sector = self.device.read(3)
		sig = sector[0:4]
		if sig!="BRFR":
			raise RuntimeError("Invalid XBOX Config Area signature: '%s'"%repr(sig))
	def gettable(self):
		table = []
		table.append(Partition(self.device,0x00000000,0x00000003-0x00000000,self.prefix+"BOOT","XBOX Legacy Boot Area (MBR)"))
		table.append(Partition(self.device,0x00000003,0x00000400-0x00000003,self.prefix+"CONFIG","XBOX Configuration Area"))
		table.append(Partition(self.device,0x00000400,0x00177400-0x00000400,self.prefix+"X","XBOX Game Cache 1"))
		table.append(Partition(self.device,0x00177400,0x002EE400-0x00177400,self.prefix+"Y","XBOX Game Cache 2"))
		table.append(Partition(self.device,0x002EE400,0x00465400-0x002EE400,self.prefix+"Z","XBOX Game Cache 3"))
		table.append(Partition(self.device,0x00465400,0x0055F400-0x00465400,self.prefix+"C","XBOX System"))
		table.append(Partition(self.device,0x0055F400,0x00EE8AB0-0x0055F400,self.prefix+"E","XBOX Data"))
		
		if self.devicesize > 0x00EE8AB0:
			if self.devicesize <= 0x10000000:
				table.append(Partition(self.device,0x00EE8AB0,self.devicesize-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
			else:
				if self.useFG:
					table.append(Partition(self.device,0x00EE8AB0,0x10000000-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
					table.append(Partition(self.device,0x10000000,self.devicesize-0x10000000,self.prefix+"G","XBOX LBA48 Extended"))
				else:
					table.append(Partition(self.device,0x00EE8AB0,self.devicesize-0x00EE8AB0,self.prefix+"F","XBOX Extended"))
			
		
		return table

class Filesystem:
	def __init__(self, partition):
		self.partition = partition


class FATX(Filesystem):
	def __init__(self, partition):
		Filesystem.__init__(self, partition)
		self.bootblock = partition.read(0)
		self.justFormatted = False
		if self.bootblock[0:4] != "FATX":
			
			print "This FATX filesystem is not formatted."
			yesno = raw_input("Do you want to format it? (Yes/No) (You need to type 'Yes'): ")
			if yesno == "Yes":
				print "Writing header..."
				import random
				self.bootblock = struct.pack("<4sIIHI","FATX",random.randint(0,2**32-1),32,1,0)
				self.bootblock += "\xff"*0xFEE
				self.partition.mwrite(0,self.bootblock)
				print "Writing FAT..."
				self.readfsinfo()
				self.showinfo()
				self.fat = array.array(self.arraycode,((self.dataclusters+1))*[self.fat_unused])
				self.fat[0] = self.fat_eoc
				self.fat[1] = self.fat_eoc_set # let's do things the MS way...
				fatstring = self.fat.tostring()
				fatstring += ((self.fatsize*512)-len(fatstring))*"\xff"
				self.partition.mwrite(8,fatstring)
				print "Writing Root Directory..."
				self.putcluster(self.rootdir,"\xff"*self.clustersize*512)
				print "Done!"
				self.justFormatted = True
				return
					
			else:
				raise NonFatalFilesystemError("Invalid FATX signature")
		
		self.readfsinfo()
	
	def readfsinfo(self):
		self.volumeid, self.clustersize, self.fatcopies = struct.unpack("IIH",self.bootblock[4:14])
		if self.fatcopies != 1:
			raise NonFatalFilesystemError("FATX has %d FAT copies, only 1 supported"%self.fatcopies)
		self.numclusters = self.partition.length / self.clustersize
		if self.numclusters >= 0xfff4:
			self.fatbits = 32
			self.arraycode = "I"
			self.fat_unused = 0x00000000L
			self.fat_eoc = 0xfffffff8L
			self.fat_eoc_set = 0xffffffffL
			self.fat_reserved = 0xfffffff0L
			self.fat_bad = 0xfffffff7L
		else:
			self.fatbits = 16
			self.fat_unused = 0x0000
			self.fat_eoc = 0xfff8
			self.fat_eoc_set = 0xffff
			self.fat_reserved = 0xfff0
			self.fat_bad = 0xfff7
			self.arraycode = "H"
		
		self.fatsize = self.numclusters * self.fatbits/8
		if self.fatsize % 4096 != 0:
			self.fatsize = ((self.fatsize / 4096) + 1) * 4096
		self.fatsize /= 512
		
		self.dataoffset = self.fatsize + 8 - self.clustersize
		self.rootdir = 1
		self.dataclusters = (self.partition.length - self.fatsize - 8)/self.clustersize
		
		#print dump(self.partition.mread(8,2))
	
	def getcluster(self, num):
		if num < 1:
			raise ValueError("Invalid FAT cluster number %d"%(num))
		return self.partition.mread(self.dataoffset+(num)*self.clustersize, self.clustersize)
	
	def putcluster(self, num, data):
		if num < 1:
			raise ValueError("Invalid FAT cluster number %d"%(num))
		if len(data) != 512*self.clustersize:
			raise ValueError("Invalid FAT cluster size %d"%(num))
		self.partition.mwrite(self.dataoffset+(num)*self.clustersize, data)
		
	def showinfo(self):
		print "  FATX volume on %s: %.1fMB raw size"%(self.partition.name,self.partition.length/2048.0)
		print "  Volume ID: %08X"%self.volumeid
		print "  Cluster size: %d sectors (%dKB)"%(self.clustersize,self.clustersize/2)
		print "  FAT type: %d bits"%(self.fatbits)
		print "  FAT copies: %d"%(self.fatcopies)
		print "  FAT size: %d sectors (%dKB)"%(self.fatsize,self.fatsize/2)
		print "  Total clusters: %d (%d data clusters)"%(self.numclusters,self.dataclusters)

	def readchain(self, cluster):
		data = ""
		while cluster < self.fat_eoc:
			#print "Reading chain, cluster = %d"%cluster
			d = self.getcluster(cluster)
			data += d
			#print "Cluster length: %d"%len(d)
			cluster = self.fat[cluster]
		#print "Chain length: %d"%len(data)
		return data

	def writechain(self, cluster, data):
		while cluster < self.fat_eoc:
			self.putcluster(cluster, data[:self.clustersize*512])
			data = data[self.clustersize*512:]
			cluster = self.fat[cluster]
		if data != "":
			raise RuntimeError("Chain length mismatch when writing chain!")

	def extendchain(self, cluster):
		#print "Extending cluster chain %d..."%cluster
		if cluster == self.rootdir:
			raise RuntimeError("Root directory is full and cannot be extended! (Or so I hear, it may very well be possible)")
		while self.fat[cluster] < self.fat_eoc:
			cluster = self.fat[cluster]
		#print "- Start %d"%cluster
		for newcluster in xrange(len(self.fat)):
			if self.fat[newcluster] == self.fat_unused:
				print "- New %d"%newcluster
				self.fat[newcluster] = self.fat_eoc_set
				self.fat[cluster] = newcluster
				return
		else:
			raise RuntimeError("No free clusters available")

	def finddir(self, path):
		cd = self.rootdir
		path = path.replace("\\","/")
		
		for element in path.split("/"):
			if element == "":
				continue
			dirdata = self.readchain(cd)
			for i in xrange(len(dirdata)/64):
				namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
				if namesz == 0xff or namesz == 0x00:
					return RuntimeError("Path element %s not found"%element)
				if namesz > 42:
					continue
				name = name[:namesz]
				if name.lower() == element.lower():
					cd = cluster
					break
			else:
				raise RuntimeError("Path element %s not found"%element)
		return cd

	def addfile(self, dircluster, filename, start, length):
		dirdata = self.readchain(dircluster)
		#print "SDir: %d"%len(dirdata)
		dirdataextension = 0

		if len(filename) > 42:
			raise RuntimeError("Filename length exceeds maximum of 42: %s (%d)"%(filename,len(filename)))
		pfilename = filename+"\xFF"*(42-len(filename))
		# first check for existing file
		for i in xrange(len(dirdata)/64):
			namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
			if namesz == 0xff or namesz == 0:
				break
			if namesz > 42:
				continue
			name = name[:namesz]
			if name.lower() == filename.lower():
				raise RuntimeError("File %s already exists!"%filename)
		# Now store the file
		for i in xrange(len(dirdata)/64):
			namesz,attr,name,cluster,size,mtime,ctime,atime = struct.unpack("<BB42sIIIII",dirdata[i*64:i*64+64])
			if namesz == 0xff or namesz == 0x00:
				dirdata = dirdata[:i*64] + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + dirdata[i*64+64:]
				#print "A Dir: %d"%len(dirdata)
				if i == ((len(dirdata)/64) - 1):
					dirdata += "\xFF"*(self.clustersize * 512)
					dirdataextension += 1
					#print "End-extending dir: %d %d"%(len(dirdata),dirdataextension)
				dirdata = dirdata[:i*64+64] + 64*"\xFF" + dirdata[i*64+128:]
				#print "B Dir: %d"%len(dirdata)
				break
			if namesz > 42:
				dirdata = dirdata[:i*64] + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + dirdata[i*64+64:]
				break
		else:
			dirdataextension += 1
			dirdata = dirdata + struct.pack("<BB42sIIIII",len(filename),0,pfilename,start,length,0,0,0) + \
				"\xFF"*(self.clustersize * 512 - 64)
			#print "Extending dir: %d %d"%(len(dirdata),dirdataextension)

		for i in xrange(dirdataextension):
			self.extendchain(dircluster)
		#print "EDir: %d"%len(dirdata)
		self.writechain(dircluster,dirdata)

	def reserve_area(self):
		
		print "Reading FAT...",
		sys.stdout.flush()
		
		start = 8
		sleft = self.fatsize - 8
		left = self.fatsize - 8
		
		np = 0.1
		
		fstr = ""
		
		self.fat = array.array(self.arraycode)
		
		while left > 0:
			qty = min(left,1024)
			s = self.partition.mread(start,qty)
			self.fat.fromstring(s)
			if self.fatsize > 20000 and np < (1-(float(left)/sleft)):
				print ("%d%%"%int((np+0.001)*100)),
				sys.stdout.flush()
				np += 0.1
			start += qty
			left -= qty
		
		print "Done."
		print "Analyzing free space...",
		sys.stdout.flush()
		
		self.freegroups = []
		self.totalfree = 0
		lastfree = False
		freestart = 0
		np = 0.1
		
		for cluster,value in enumerate(self.fat):
			if cluster % 4096 == 0:
				if np < cluster/float(self.numclusters):
					print ("%d%%"%int((np+0.001)*100)),
					sys.stdout.flush()
					np += 0.1

			if value == self.fat_unused:
				self.totalfree += 1
				if not lastfree:
					freestart = cluster
					lastfree = True
			elif lastfree:
					self.freegroups.append((freestart, cluster-freestart))
					lastfree = False
		if lastfree:
			self.freegroups.append((freestart, cluster+1-freestart))
		
		print "Done."
		
		print "%d clusters free (%.1fMB)"%(self.totalfree, (self.totalfree*self.clustersize)/2048.0)
		
		if self.totalfree == 0:
			print "No space available!"
			return False
		
		self.freegroups.sort(lambda x,y: cmp(x[1],y[1]))
		maxblock = self.freegroups[-1][1]
		print "Largest contiguous block available: %d clusters (%.1fMB)"%(maxblock,maxblock*self.clustersize/2048.0)
		
		havevalue = False
		
		while True:
			if not havevalue:
				value = raw_input("Target partition size (#{K,M,G}): ")
			havevalue = False
			mult = 1
			if not value:
				continue
			if value[-1] == "B":
				value = value[:-1]
			if not value:
				print "Invalid size value."
				continue
			if value[-1] in "KMG":
				if value[-1] == "K":
					mult = 1024
				elif value[-1] == "M":
					mult = 1024*1024
				elif value[-1] == "G":
					mult = 1024*1024*1024
				value=value[:-1]
			if not value:
				print "Invalid size value."
				continue
			try:
				numbytes = int(value)
			except ValueError:
				print "Invalid size value."
				continue
			numbytes *= mult
			if numbytes % 512 != 0:
				numsectors = numbytes / 512 + 1
				numbytes = numsectors * 512
			else:
				numsectors = numbytes / 512
			if numsectors % self.clustersize != 0:
				numclusters = numsectors / self.clustersize + 1
			else:
				numclusters = numsectors / self.clustersize
			
			if numclusters > maxblock:
				print "Size is too large"
				continue
			print "You requested %d sectors (%.1fMB)"%(numsectors, numsectors/2048.0)
			
			nextlarger = len(self.freegroups)-1
			nextsmaller = 0
			if numbytes < self.freegroups[nextsmaller][1]*self.clustersize*512:
				nextsmaller = None
			else:
				while numbytes >= self.freegroups[nextsmaller][1]*self.clustersize*512:
					nextsmaller += 1
				nextsmaller -= 1
			while numbytes <= self.freegroups[nextlarger][1]*self.clustersize*512:
				nextlarger -= 1
				if nextlarger == -1:
					break
			nextlarger += 1
			
			print "Closest contiguous block larger than the requested size: %d clusters (%.1fMB)"%\
				(self.freegroups[nextlarger][1],self.freegroups[nextlarger][1]*self.clustersize/2048.0)
			if nextsmaller is not None:	
				print "Closest contiguous block smaller than the requested size: %d clusters (%.1fMB)"%\
					(self.freegroups[nextsmaller][1],self.freegroups[nextsmaller][1]*self.clustersize/2048.0)
			
			print "- Enter L to use the size of the closest larger contiguous block"
			if nextsmaller is not None:	
				print "- Enter S to use the size of the closest smaller contiguous block"
			print "- Enter T to use the specified target size"
			if numsectors % self.clustersize != 0:
				print "- Enter R to use the specified target size, rounded to the next cluster (this is free)"
			value = raw_input("- Or enter a new target size value (#{K,M,G}): ")
			
			if value in "lL":
				block = self.freegroups[nextlarger]
			elif value in "sS":
				block = self.freegroups[nextsmaller]
			elif value in "tTrR":
				block = self.freegroups[nextlarger][0],numclusters
				if value in "rR":
					numsectors = numclusters * self.clustersize
			else:
				havevalue = True
				continue
			
			if (self.totalfree-block[1])*self.clustersize < 2048:
				print
				print "You should probably leave some free space. Who knows what will happen otherwise."
				print "Try again."
				continue
			
			kpercluster = self.clustersize / 2
			
			allocbytes = block[1] * self.clustersize * 512
			if numbytes >= 2**31-1:
				print
				print "The requested size will not fit into a standard file in FATX."
				print "Please choose among the following options:"
				print " A) Use multiple 1GB files. This is safe."
				print " B) Use multiple 2GB-%dK files. This is safe, but not very neat."%kpercluster
				print " C) Use multiple 2GB files. This will show up as -2GB using the MS kernel."
				print " D) Use multiple 4GB-%dK files. This will show up as -%dK using the MS kernel."%(kpercluster,kpercluster)
				print " E) Choose a new partition size."
				choice = raw_input("Your choice: ")
				while choice not in "ABCDEabcde":
					print "Invalid choice %s"%choice
					choice = raw_input("Your choice: ")
				if choice in "eE":
					continue
				elif choice in "aA":
					chunksize = 2048*1024/self.clustersize
				elif choice in "bB":
					chunksize = (4096*1024/self.clustersize)-1
				elif choice in "cC":
					chunksize = 4096*1024/self.clustersize
				elif choice in "dD":
					chunksize = (8096*1024/self.clustersize)-1
					
				chunksrequired = numbytes / (chunksize*self.clustersize*512)
				if numbytes % (chunksize*self.clustersize*512) != 0:
					chunksrequired += 1
				print
				print "%d file(s) will be created."%chunksrequired
				
				filetab = []
				
				for filenum in xrange(chunksrequired):
					start = filenum*chunksize + block[0]
					length = min(chunksize, block[1]-filenum*chunksize)
					bytelength = min(chunksize*self.clustersize,numsectors-(filenum*chunksize*self.clustersize))*512
					
					print "Allocating %d clusters (%d - %d) for file %d (%.1fMB)..."%\
						(length, start, start+length-1, filenum, length*self.clustersize/2048.0)
						
					filetab.append((start, bytelength))
					
					for cluster in xrange(start, start+length):
						if cluster == (start+length-1):
							self.fat[cluster] = self.fat_eoc_set
						else:
							self.fat[cluster] = cluster+1
				
				print
				path = raw_input("Enter the path where the files will be stored: ")

				dircluster = self.finddir(path)
				
				base = raw_input("Enter the basename for the files: ")

				print "Writing %d files..."%chunksrequired
				
				for file,entry in enumerate(filetab):
					filename = base + ".%03d"%file
					print " %s"%filename
					start, length = entry
					self.addfile(dircluster,filename,start,length)
				
			else:
				print "Allocating %d clusters (%d - %d, %.1fMB)..."%(numclusters, block[0],block[0]+block[1]-1,allocbytes/1024.0/1024.0)
				for cluster in xrange(block[0], block[0]+block[1]):
					if cluster == (block[0]+block[1]):
						self.fat[cluster] = self.fat_eoc_set
					else:
						self.fat[cluster] = cluster+1
						
				print
				path = raw_input("Enter the path where the file will be stored: ")
				dircluster = self.finddir(path)
				filename = raw_input("Enter the filename: ")
				print "Writing file %s..."%filename	
				self.addfile(dircluster,filename,block[0],numbytes)
			
			print "Storing FAT back..."
			
			fatstring = self.fat.tostring()
			
			self.fat = None

			fatstring += ((self.fatsize*512)-len(fatstring))*"\xff"
			
			self.partition.mwrite(8,fatstring)
			
			print "Done!"
			
			return (block[0]*self.clustersize+self.dataoffset, block[1]*self.clustersize)

def main(argv):
	
	print "subfdisk "+VERSION+" by Hector Martin <hector@marcansoft.com>"
	
	if len(argv) < 2 or len(argv) > 3:
		print "Usage: python %s [-g] <device>"%argv[0]
		print "<device> should be the entire hard disk device, not a partition."
		print "-g: use split F/G partitions for HDDs larger than 128GiB/137GB"
		return
	
	useFG = False
	
	if argv[1] == "-g":
		useFG = True
		argv=argv[0:1]+argv[2:]
	
	print
	device = BlockDevice(argv[1])
	print "%s: %d bytes (%.1fMB)"%(argv[1],device.size()*device.sectorsize,device.size()*device.sectorsize/1024.0/1024.0)
	print
	
	print "Reading partitions..."
	
	while True:
		
		partitions = PartitionCollection(device)
		partitions.register(XBOXPartitionTable(device,useFG))
		partitions.register(MBRPartitionTable(device))
		print str(partitions)
		
		while True:
			part = raw_input("Choose the partition you want to work with: ")
			if not part: continue
			if part == "q": return
			try:
				numpart = int(part)-1
			except ValueError:
				for numpart,partition in enumerate(partitions):
					if partition.name == part:
						break
				else:
					print "Invalid partition name/number: %s"%part
					continue
			if numpart >= len(partitions) or numpart < 0:
				print "Invalid partition name/number: %s"%part
				continue
			break
		
		print 
		print "Partition %d chosen (%s)"%(numpart+1,partitions[numpart].name)
		print "Loading filesystem...",
		try:
			fs = FATX(partitions[numpart])
			if not fs.justFormatted:
					
				print
				print "Filesystem Information:"
				fs.showinfo()
				print
				area = fs.reserve_area()
				if not area:
					continue
				start,length = area
				start += partitions[numpart].start
				print "MBR Partitions:"
				mbrpt = MBRPartitionTable(device)
				parts = mbrpt.gettable()
				if len(parts) == 0:
					print "  [No MBR Partitions]"
				else:
					for i in mbrpt.gettable():
						print "  "+str(i)
				
				while True:
					pnum = raw_input("Enter the partition number which you want to create: ")
					try:
						pnum = int(pnum)
						if pnum > 4 or pnum < 1:
							raise ValueError("blah")
						break
					except ValueError:
						print "Invalid value."
						continue
					
					if mbrpt.hasentry(pnum-1):
						yesno = raw_input("Partition table entry %d already exists! Do you want to overwrite it? (Y/N): ")
						if yesno in "Yy":
							break
					else:
						break
				
				while True:
					ptype = raw_input("Enter the type for the partition (82 for Linux swap, 83 for Linux, etc): ")
					try:
						ptype = int(ptype,16)
						if ptype == 0:
							raise ValueError("blah")
						break
					except ValueError:
						print "Invalid value."
						continue
				
				mbrpt.writeentry(pnum-1,0x00,ptype,start,length)
				
				print "Done!"
				print
				
				partitions = PartitionCollection(device)
				partitions.register(XBOXPartitionTable(device))
				partitions.register(MBRPartitionTable(device))
				print str(partitions)
				
		except NonFatalFilesystemError:
			t,v,tb = sys.exc_info()
			print v
			print
			print "Press enter to continue"
			raw_input()
			continue
			
		while True:
			yesno = raw_input("Do you want to perform other operations? (Y/N): ")
			if yesno in "YyNn":
				break
		if yesno in "Yy":
			continue
		else:
			#device.dumpchanges()
			yesno = raw_input("Commit changes? WARNING: THIS OPERATION IS NOT REVERSIBLE (Yes/No) (You need to type 'Yes'): ")
			if yesno == "Yes":
				print "COMMITTING CHANGES!"
				device.commit()
				print "Done."
				print
				print "REMEMBER TO RUN hdparm -z %s OR REBOOT LINUX TO REREAD THE PARTITION TABLE!"%argv[1]
			break
			

if __name__=="__main__":
	try:
		main(sys.argv)
	except MemoryError:
		print
		print "Out of Memory."
		print "You probably tried to format or work with a large partition from an Xbox LiveCD."
		print "As you probably already know, the Xbox contains only 64MB of RAM, part of which"
		print "is required to run the LiveCD. subfdisk needs to keep all changes in RAM for"
		print "safety, but the FAT of large partitions can easily be very large, exceeding all"
		print "available RAM."
		print
		print "Suggestion: enable some swapspace. One good place to do that is the game cache"
		print "FATX partitions (X/Y/Z). If you have just created the BRFR signature, Linux"
		print "will not know about these partitions yet. Try `hdparm -z /dev/hda' or reboot your"
		print "Xbox. Once you have done that, run `mkswap /dev/hda53 && swapon /dev/hda53' to"
		print "enable some swap. Once you are done, you can disable swap again and reformat"
		print "/dev/hda53 as FATX again, using mkfs.fatx or this program (just pick the"
		print "XBOX-Y partition and you will be prompted). Make sure you only do this after"
		print "disabling swap with `swapoff /dev/hda53'!"
