Guest User

gdishrink

a guest
Mar 27th, 2015
44
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.50 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. import sys, os, shutil
  5. try:
  6.     from cStringIO import StringIO
  7. except ImportError:
  8.     from StringIO import StringIO
  9.  
  10. def gdishrink(gdi):
  11.     l = loadgdi(gdi)
  12.     update_gdi(gdi)
  13.     if l[0][0] == 3:
  14.         gdishrink_three(l)
  15.     else:
  16.         gdishrink_cdda(l)
  17.        
  18. def gdishrink_three(l):
  19.     print '3-Tracks GDI are unsupported for now...'
  20.     pass
  21.  
  22. def gdishrink_cdda(l):
  23.     nbt = int(l[0][0])
  24.     modes = [2048 if l[i][3] == '2048' else 2352 for i in 1,3,nbt]
  25.     fn = outname(l[3][4])
  26.    
  27.     with CdImage(l[3][4], modes[1]) as f:
  28.         length = rFNES(f)
  29.         f.seek(0,0)
  30.         with open(fn, 'wb') as g:
  31.             _copy_buffered(f, g, length = length)
  32.     for j in l[1][4],l[nbt][4]:            
  33.         with CdImage(j, modes[0]) as f, open(outname(j),'wb') as g:
  34.             _copy_buffered(f,g)
  35.  
  36.  
  37.    
  38. def loadgdi(gdi):
  39.     with open(gdi) as g:
  40.         l = [i.split() for i in g.readlines() if i.split()]
  41.     if not int(l[3][1]) == 45000:
  42.         raise AssertionError('Invalid gdi file: track03 LBA should be 45000')
  43.     return l
  44.    
  45. def bakfile(fn):
  46.     if not os.path.isfile(fn+'.bak'):
  47.         shutil.copy(fn, fn+'.bak')
  48.     if not os.path.isfile(fn+'.bak'):
  49.         raise IOError('Could not backup {} properly!'.format(fn))
  50.  
  51. def backupGDI(gdi, l=None):
  52.     bakfile(gdi)
  53.     if l == None:
  54.         l = loadgdi(gdi)
  55.     nbt = int(l[0][0])
  56.     bakfile(l[1][4])
  57.     t3name = l[3][4]
  58.     bakfile(t3name)
  59.     if nbt > 3:
  60.         txname = l[nbt][4]
  61.         bakfile(txname)
  62.  
  63. def update_gdi(gdi):
  64.     l=loadgdi(gdi)
  65.     nbt = int(l[0][0])
  66.     backupGDI(gdi, l=l)
  67.     tracks = 1,3 if nbt == 3 else 1,3,nbt
  68.    
  69.     for t in tracks:
  70.         l[t][3] = '2048'
  71.         l[t][4] = outname(l[t][4])
  72.  
  73.     with open(gdi, 'wb') as f:
  74.         f.write('\n'.join([i for i in [' '.join(j) for j in l]]))
  75.  
  76. def outname(i):
  77.     o = i.replace('.bin','').replace('.BIN','')
  78.     if not o[-4:] == '.iso':
  79.         o += '.iso'
  80.     return o
  81.            
  82.  
  83. def rFNEB(f, b=2048, offset=None): # Reverse find non-empty block
  84.     if offset == None:
  85.         f.seek(0,2)
  86.         offset = f.tell()
  87.     empty = '\x00'*b
  88.     loc = False
  89.     while offset > 0:
  90.         f.seek(offset-b if b<offset else 0)
  91.         if f.read(b) == empty:
  92.             offset -= b
  93.         else:
  94.             loc = f.tell()
  95.             break
  96.     return loc
  97.    
  98. def rFNES(f, offset=None):  # Adaptively Reverse find non-empty 2048 kB sector
  99.     # Blocks are 20MB, 10MB, 1MB, 100kB, 10kB, 2kB.
  100.     # The idea is to reduce seeking.
  101.     b = [1024*int(i) for i in [2e4, 1e4, 1e3, 1e2, 1e1,2]]
  102.     loc = offset
  103.     for i in b:
  104.         loc=rFNEB(f, b=i, offset=loc)
  105.     return loc
  106.    
  107.  
  108.  
  109. # Class CdImage was taken from the gditools project and is gplv3 licensed
  110. class CdImage(file):
  111.     """
  112.    Class that allows opening a 2352 or 2048 bytes/sector data cd track
  113.    as a 2048 bytes/sector one.
  114.    """
  115.     def __init__(self, filename, mode = 'auto', *args, **kwargs):
  116.  
  117.         if mode == 'auto':
  118.             if filename[-4:] == '.iso': mode = 2048
  119.             elif filename[-4:] == '.bin': mode = 2352
  120.  
  121.         elif not mode in [2048, 2352]:
  122.             raise ValueError('Argument mode should be either 2048 or 2352')
  123.         self.__mode = mode
  124.  
  125.         if (len(args) > 0) and (args[0] not in ['r','rb']):
  126.             raise NotImplementedError('Only read mode is implemented.')
  127.  
  128.         file.__init__(self, filename, 'rb')
  129.  
  130.         file.seek(self,0,2)
  131.         if self.__mode == 2352:
  132.             self.length = file.tell(self) * 2048/2352
  133.         else:
  134.             self.length = file.tell(self)
  135.         file.seek(self,0,0)
  136.  
  137.         self.seek(0)
  138.  
  139.     def realOffset(self,a):
  140.         return a/2048*2352 + a%2048 + 16
  141.  
  142.     def seek(self, a, b = 0):
  143.         if self.__mode == 2048:
  144.             file.seek(self, a, b)
  145.  
  146.         elif self.__mode == 2352:
  147.             if b == 0:
  148.                 self.binpointer = a
  149.             if b == 1:
  150.                 self.binpointer += a
  151.             if b == 2:
  152.                 self.binpointer = self.length - a
  153.  
  154.             realpointer = self.realOffset(self.binpointer)
  155.             file.seek(self, realpointer, 0)
  156.  
  157.     def read(self, length = None):
  158.         if self.__mode == 2048:
  159.             return file.read(self, length)
  160.  
  161.         elif self.__mode == 2352:
  162.             if length == None:
  163.                 length = self.length - self.binpointer
  164.  
  165.             # Amount of bytes left until beginning of next sector
  166.             tmp = 2048 - self.binpointer % 2048    
  167.             FutureOffset = self.binpointer + length
  168.             realLength = self.realOffset(FutureOffset) - \
  169.                             self.realOffset(self.binpointer)
  170.             # This will (hopefully) accelerates readings on HDDs at the
  171.             # cost of more memory use.
  172.             buff = StringIO(file.read(self, realLength))
  173.             # The first read can be < 2048 bytes
  174.             data = buff.read(tmp)
  175.             length -= tmp
  176.             buff.seek(304, 1)
  177.             # The middle reads are all 2048 so we optimize here!
  178.             for i in xrange(length / 2048):
  179.                 data += buff.read(2048)
  180.                 buff.seek(304, 1)
  181.             # The last read can be < 2048 bytes
  182.             data += buff.read(length % 2048)
  183.             # Seek back to where we should be
  184.             self.seek(FutureOffset)
  185.             return data
  186.  
  187.     def tell(self):
  188.         if self.__mode == 2048:
  189.             return file.tell(self)
  190.  
  191.         elif self.__mode == 2352:
  192.             return self.binpointer
  193.            
  194. # Function _copy_buffered was taken from the gditools project and is gplv3 licensed
  195. def _copy_buffered(f1, f2, length = False, bufsize = 1*1024*1024, closeOut = True):
  196.     """
  197.    Copy istream f1 into ostream f2 in bufsize chunks
  198.    """
  199.     if not length:  # By default it read all the file
  200.         tmp = f1.tell()
  201.         f1.seek(0,2)
  202.         length = f1.tell()
  203.         f1.seek(tmp,0)
  204.     f2.seek(0,0)
  205.  
  206.     for i in xrange(length/bufsize):
  207.         f2.write(f1.read(bufsize))
  208.     f2.write(f1.read(length % bufsize))
  209.  
  210.     if closeOut:
  211.         f2.close()
  212.    
  213.  
  214.    
  215. def main(argv):
  216.     if len(argv) == 2:
  217.         gdiname = argv[1]
  218.         owd = os.getcwd()
  219.         os.chdir(os.path.dirname(gdiname))
  220.         gdishrink(os.path.basename(gdiname))
  221.         os.chdir(owd)
  222.     else:
  223.         print "Usage:\n\tgdishrink relative/or/absolute/path/to/disc.gdi"
  224.    
  225.    
  226. if __name__ == '__main__':
  227.     main(sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment