Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- # -*- coding: utf-8 -*-
- # A simple program to remove padding sectors from bin files.
- # Aimed at shrinking gdi files.
- #
- # Licensed under GPLv3
- #
- # by FamilyGuy, 2015
- import os, sys, shutil
- try:
- from cStringIO import StringIO
- except ImportError:
- from StringIO import StringIO
- def unpad_bin_file(ifname, ofname, verbose=False):
- with CdImage(ifname, mode = 2352) as f:
- pad_start = rFNES(f)/2048*2352
- with open(ifname, 'rb') as i, open(ofname, 'wb') as o:
- i.seek(0,2)
- isize = i.tell()
- i.seek(0,0)
- _copy_buffered(i, o, length = pad_start, closeOut=False)
- o.seek(0,2)
- osize = o.tell()
- o.seek(0,0)
- if verbose:
- s = '\nOriginal size \t:\t{} MiB'.format(isize/2**20) +\
- '\nFinal size\t:\t{} MiB\n'.format(osize/2**20) +\
- '\n{} MB saved!\n'.format((isize-osize)/2**20) +\
- '\nFamilyGuy 2015, licensed under GPLv3\n'
- print s
- def rFNEB(f, b=2048, offset=None): # Reverse find non-empty block
- if offset == None:
- f.seek(0,2)
- offset = f.tell()
- empty = '\x00'*b
- loc = False
- while offset > 0:
- f.seek(offset-b if b<offset else 0)
- if f.read(b) == empty:
- offset -= b
- else:
- loc = f.tell()
- break
- return loc
- def rFNES(f, offset=None): # Adaptively Reverse find non-empty 2048 kB sector
- # Blocks are 20MB, 10MB, 1MB, 100kB, 10kB, 2kB.
- # The idea is to reduce seeking.
- b = [1024*int(i) for i in [2e4, 1e4, 1e3, 1e2, 1e1,2]]
- loc = offset
- for i in b:
- loc=rFNEB(f, b=i, offset=loc)
- return loc
- # Class CdImage was taken from the gditools project and is gplv3 licensed
- class CdImage(file):
- """
- Class that allows opening a 2352 or 2048 bytes/sector data cd track
- as a 2048 bytes/sector one.
- """
- def __init__(self, filename, mode = 'auto', *args, **kwargs):
- if mode == 'auto':
- if filename[-4:] == '.iso': mode = 2048
- elif filename[-4:] == '.bin': mode = 2352
- elif not mode in [2048, 2352]:
- raise ValueError('Argument mode should be either 2048 or 2352')
- self.__mode = mode
- if (len(args) > 0) and (args[0] not in ['r','rb']):
- raise NotImplementedError('Only read mode is implemented.')
- file.__init__(self, filename, 'rb')
- file.seek(self,0,2)
- if self.__mode == 2352:
- self.length = file.tell(self) * 2048/2352
- else:
- self.length = file.tell(self)
- file.seek(self,0,0)
- self.seek(0)
- def realOffset(self,a):
- return a/2048*2352 + a%2048 + 16
- def seek(self, a, b = 0):
- if self.__mode == 2048:
- file.seek(self, a, b)
- elif self.__mode == 2352:
- if b == 0:
- self.binpointer = a
- if b == 1:
- self.binpointer += a
- if b == 2:
- self.binpointer = self.length - a
- realpointer = self.realOffset(self.binpointer)
- file.seek(self, realpointer, 0)
- def read(self, length = None):
- if self.__mode == 2048:
- return file.read(self, length)
- elif self.__mode == 2352:
- if length == None:
- length = self.length - self.binpointer
- # Amount of bytes left until beginning of next sector
- tmp = 2048 - self.binpointer % 2048
- FutureOffset = self.binpointer + length
- realLength = self.realOffset(FutureOffset) - \
- self.realOffset(self.binpointer)
- # This will (hopefully) accelerates readings on HDDs at the
- # cost of more memory use.
- buff = StringIO(file.read(self, realLength))
- # The first read can be < 2048 bytes
- data = buff.read(tmp)
- length -= tmp
- buff.seek(304, 1)
- # The middle reads are all 2048 so we optimize here!
- for i in xrange(length / 2048):
- data += buff.read(2048)
- buff.seek(304, 1)
- # The last read can be < 2048 bytes
- data += buff.read(length % 2048)
- # Seek back to where we should be
- self.seek(FutureOffset)
- return data
- def tell(self):
- if self.__mode == 2048:
- return file.tell(self)
- elif self.__mode == 2352:
- return self.binpointer
- # Function _copy_buffered was taken from the gditools project and is gplv3 licensed
- def _copy_buffered(f1, f2, length = False, bufsize = 1*1024*1024, closeOut = True):
- """
- Copy istream f1 into ostream f2 in bufsize chunks
- """
- if not length: # By default it read all the file
- tmp = f1.tell()
- f1.seek(0,2)
- length = f1.tell()
- f1.seek(tmp,0)
- f2.seek(0,0)
- for i in xrange(length/bufsize):
- f2.write(f1.read(bufsize))
- f2.write(f1.read(length % bufsize))
- if closeOut:
- f2.close()
- def main(argv):
- if len(argv) == 2:
- fname = argv[1]
- basedir = os.path.dirname(fname)
- if not basedir == '':
- os.chdir(basedir)
- fname = os.path.basename(fname)
- if not os.path.isfile(fname+'.bak'): # Avoids overwriting a backup
- shutil.move(fname, fname+'.bak')
- if not os.path.isfile(fname+'.bak'): # Make sure it worked fine
- raise IOError('Could not backup {} properly!'.format(fname))
- unpad_bin_file(fname+'.bak', fname, verbose = True)
- else:
- print '\nUsage:\n\tunpadbinfile file.bin\n\nFamilyGuy 2015, licensed under GPLv3\n'
- if __name__ == '__main__':
- main(sys.argv)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement