Advertisement
Guest User

unpadbinfile

a guest
Mar 27th, 2015
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.80 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. # A simple program to remove padding sectors from bin files.
  5. # Aimed at shrinking gdi files.
  6. #
  7. # Licensed under GPLv3
  8. #
  9. # by FamilyGuy, 2015
  10.  
  11. import os, sys, shutil
  12. try:
  13.     from cStringIO import StringIO
  14. except ImportError:
  15.     from StringIO import StringIO
  16.  
  17. def unpad_bin_file(ifname, ofname, verbose=False):
  18.     with CdImage(ifname, mode = 2352) as f:
  19.         pad_start = rFNES(f)/2048*2352
  20.     with open(ifname, 'rb') as i, open(ofname, 'wb') as o:
  21.         i.seek(0,2)
  22.         isize = i.tell()
  23.         i.seek(0,0)
  24.         _copy_buffered(i, o, length = pad_start, closeOut=False)
  25.         o.seek(0,2)
  26.         osize = o.tell()
  27.         o.seek(0,0)
  28.     if verbose:
  29.         s = '\nOriginal size \t:\t{} MiB'.format(isize/2**20)   +\
  30.             '\nFinal size\t:\t{} MiB\n'.format(osize/2**20)     +\
  31.             '\n{} MB saved!\n'.format((isize-osize)/2**20)      +\
  32.             '\nFamilyGuy 2015, licensed under GPLv3\n'
  33.         print s
  34.        
  35.  
  36.  
  37. def rFNEB(f, b=2048, offset=None): # Reverse find non-empty block
  38.     if offset == None:
  39.         f.seek(0,2)
  40.         offset = f.tell()
  41.     empty = '\x00'*b
  42.     loc = False
  43.     while offset > 0:
  44.         f.seek(offset-b if b<offset else 0)
  45.         if f.read(b) == empty:
  46.             offset -= b
  47.         else:
  48.             loc = f.tell()
  49.             break
  50.     return loc
  51.    
  52. def rFNES(f, offset=None):  # Adaptively Reverse find non-empty 2048 kB sector
  53.     # Blocks are 20MB, 10MB, 1MB, 100kB, 10kB, 2kB.
  54.     # The idea is to reduce seeking.
  55.     b = [1024*int(i) for i in [2e4, 1e4, 1e3, 1e2, 1e1,2]]
  56.     loc = offset
  57.     for i in b:
  58.         loc=rFNEB(f, b=i, offset=loc)
  59.     return loc
  60.  
  61. # Class CdImage was taken from the gditools project and is gplv3 licensed
  62. class CdImage(file):
  63.     """
  64.    Class that allows opening a 2352 or 2048 bytes/sector data cd track
  65.    as a 2048 bytes/sector one.
  66.    """
  67.     def __init__(self, filename, mode = 'auto', *args, **kwargs):
  68.  
  69.         if mode == 'auto':
  70.             if filename[-4:] == '.iso': mode = 2048
  71.             elif filename[-4:] == '.bin': mode = 2352
  72.  
  73.         elif not mode in [2048, 2352]:
  74.             raise ValueError('Argument mode should be either 2048 or 2352')
  75.         self.__mode = mode
  76.  
  77.         if (len(args) > 0) and (args[0] not in ['r','rb']):
  78.             raise NotImplementedError('Only read mode is implemented.')
  79.  
  80.         file.__init__(self, filename, 'rb')
  81.  
  82.         file.seek(self,0,2)
  83.         if self.__mode == 2352:
  84.             self.length = file.tell(self) * 2048/2352
  85.         else:
  86.             self.length = file.tell(self)
  87.         file.seek(self,0,0)
  88.  
  89.         self.seek(0)
  90.  
  91.     def realOffset(self,a):
  92.         return a/2048*2352 + a%2048 + 16
  93.  
  94.     def seek(self, a, b = 0):
  95.         if self.__mode == 2048:
  96.             file.seek(self, a, b)
  97.  
  98.         elif self.__mode == 2352:
  99.             if b == 0:
  100.                 self.binpointer = a
  101.             if b == 1:
  102.                 self.binpointer += a
  103.             if b == 2:
  104.                 self.binpointer = self.length - a
  105.  
  106.             realpointer = self.realOffset(self.binpointer)
  107.             file.seek(self, realpointer, 0)
  108.  
  109.     def read(self, length = None):
  110.         if self.__mode == 2048:
  111.             return file.read(self, length)
  112.  
  113.         elif self.__mode == 2352:
  114.             if length == None:
  115.                 length = self.length - self.binpointer
  116.  
  117.             # Amount of bytes left until beginning of next sector
  118.             tmp = 2048 - self.binpointer % 2048    
  119.             FutureOffset = self.binpointer + length
  120.             realLength = self.realOffset(FutureOffset) - \
  121.                             self.realOffset(self.binpointer)
  122.             # This will (hopefully) accelerates readings on HDDs at the
  123.             # cost of more memory use.
  124.             buff = StringIO(file.read(self, realLength))
  125.             # The first read can be < 2048 bytes
  126.             data = buff.read(tmp)
  127.             length -= tmp
  128.             buff.seek(304, 1)
  129.             # The middle reads are all 2048 so we optimize here!
  130.             for i in xrange(length / 2048):
  131.                 data += buff.read(2048)
  132.                 buff.seek(304, 1)
  133.             # The last read can be < 2048 bytes
  134.             data += buff.read(length % 2048)
  135.             # Seek back to where we should be
  136.             self.seek(FutureOffset)
  137.             return data
  138.  
  139.     def tell(self):
  140.         if self.__mode == 2048:
  141.             return file.tell(self)
  142.  
  143.         elif self.__mode == 2352:
  144.             return self.binpointer
  145.            
  146. # Function _copy_buffered was taken from the gditools project and is gplv3 licensed
  147. def _copy_buffered(f1, f2, length = False, bufsize = 1*1024*1024, closeOut = True):
  148.     """
  149.    Copy istream f1 into ostream f2 in bufsize chunks
  150.    """
  151.     if not length:  # By default it read all the file
  152.         tmp = f1.tell()
  153.         f1.seek(0,2)
  154.         length = f1.tell()
  155.         f1.seek(tmp,0)
  156.     f2.seek(0,0)
  157.  
  158.     for i in xrange(length/bufsize):
  159.         f2.write(f1.read(bufsize))
  160.     f2.write(f1.read(length % bufsize))
  161.  
  162.     if closeOut:
  163.         f2.close()
  164.  
  165.  
  166. def main(argv):
  167.     if len(argv) == 2:
  168.         fname = argv[1]
  169.         basedir = os.path.dirname(fname)
  170.         if not basedir == '':
  171.             os.chdir(basedir)
  172.         fname = os.path.basename(fname)
  173.         if not os.path.isfile(fname+'.bak'):    # Avoids overwriting a backup
  174.             shutil.move(fname, fname+'.bak')
  175.         if not os.path.isfile(fname+'.bak'):    # Make sure it worked fine
  176.             raise IOError('Could not backup {} properly!'.format(fname))
  177.         unpad_bin_file(fname+'.bak', fname, verbose = True)
  178.     else:
  179.         print '\nUsage:\n\tunpadbinfile file.bin\n\nFamilyGuy 2015, licensed under GPLv3\n'
  180.  
  181. if __name__ == '__main__':
  182.     main(sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement