uzlonewolf

unpacker.py

Sep 19th, 2021
748
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import sys
  2. import os.path
  3.  
  4. class crc16:
  5.     def __init__( self, init=0x0, poly=0x1021 ):
  6.         self.poly = poly
  7.         self.initial = init
  8.         self._tab = [ self._initial(i) for i in range(256) ]
  9.  
  10.     def _initial(self, c):
  11.         crc = 0
  12.         c = c << 8
  13.  
  14.         for j in range(8):
  15.             if (crc ^ c) & 0x8000:
  16.                 crc = (crc << 1) ^ self.poly
  17.             else:
  18.                 crc = crc << 1
  19.             c = c << 1
  20.         return crc
  21.  
  22.     def _update_crc(self, crc, c):
  23.         cc = 0xff & c
  24.         tmp = (crc >> 8) ^ cc
  25.         crc = (crc << 8) ^ self._tab[tmp & 0xff]
  26.         crc = crc & 0xffff
  27.         return crc
  28.  
  29.     def crc16(self, i):
  30.         crc = self.initial
  31.         for c in i:
  32.             crc = self._update_crc(crc, c)
  33.         return crc
  34.  
  35.  
  36. def decode_block( data ):
  37.     xor = 0xEF
  38.     lfsr_taps = 0xe03e
  39.     lfsr_state = 0x84
  40.     lfsr_xor = 0xef
  41.     out = [ ]
  42.  
  43.     for val in data:
  44.         out.append( val ^ xor )
  45.  
  46.         newbit = 0
  47.         count = ( lfsr_state & lfsr_taps ) + 1
  48.         while( count ):
  49.             count &= (count - 1)
  50.             newbit += 1
  51.  
  52.         newbit &= 1
  53.         lfsr_state >>= 1
  54.         lfsr_state |= ( newbit << 15 )
  55.  
  56.         xor *= 2
  57.         carry = xor >> 8
  58.         xor &= 0xFF
  59.  
  60.         if( newbit ^ carry ):
  61.             xor ^= 0x21
  62.  
  63.     return out
  64.  
  65. class FileTypeRaw:
  66.     def __init__( self, data, filetable ):
  67.         self.type = 'raw'
  68.         self.data = data
  69.  
  70.     def encode( self, encode ):
  71.         return self.data # never encoded
  72.  
  73. class AppTableEntry:
  74.     def __init__( self, data=None ):
  75.         self.index = 0
  76.         self.length = 0
  77.         self.zero1 = 0
  78.         self.load = 0
  79.         self.zero2 = 0
  80.         self.offset = 0
  81.         self.data_crc = 0
  82.         self.data_crc_calculated = 0
  83.         self.table_crc = 0
  84.         self.table_crc_calculated = 0
  85.         self.data = ''
  86.  
  87.         if( data ):
  88.             self.decode_entry( data )
  89.  
  90.     def decode_entry( self, data ):
  91.         if( len(data) != 16 ):
  92.             raise ValueError('App table entry block must be 16 bytes long')
  93.  
  94.         data = decode_block( data )
  95.         self.table_crc_calculated = c16.crc16( data[:-2] )
  96.  
  97.         self.index = data[0] << 8 | data[1]
  98.         self.length = data[2] << 8 | data[3]
  99.         self.zero1 = data[4] << 8 | data[5]
  100.         self.load = data[6] << 8 | data[7]
  101.         self.zero2 = data[8] << 8 | data[9]
  102.         self.start = data[10] << 8 | data[11]
  103.         self.data_crc = data[12] << 8 | data[13]
  104.         self.table_crc = data[14] << 8 | data[15]
  105.  
  106.     def encode_entry( self, encode ):
  107.         block = self.index.to_bytes(2, 'big') + self.length.to_bytes(2, 'big')
  108.         block += self.zero1.to_bytes(2, 'big') + self.load.to_bytes(2, 'big') + self.zero2.to_bytes(2, 'big')
  109.         block += self.start.to_bytes(2, 'big') + self.data_crc.to_bytes(2, 'big')
  110.  
  111.         self.table_crc = c16.crc16( block )
  112.         block += self.table_crc.to_bytes(2, 'big')
  113.  
  114.         if( encode ):
  115.             return bytes( decode_block( block ) )
  116.  
  117.         return block
  118.  
  119.     def decode_data( self, data ):
  120.         if( len(data) != self.length ):
  121.             raise ValueError('App table data block # %d must be %d bytes long' % (self.index, self.length))
  122.  
  123.         self.data = decode_block( data )
  124.         self.data_crc_calculated = c16.crc16( self.data )
  125.  
  126.     def encode_data( self, encode ):
  127.         self.data_crc = c16.crc16( self.data )
  128.  
  129.         #if( encode ):
  130.         return bytes( decode_block( self.data ) )
  131.  
  132. class FileTypeApp:
  133.     def __init__( self, data, filetable ):
  134.         self.type = 'app'
  135.         self.entries = [ ]
  136.  
  137.         self.decode_file( data )
  138.  
  139.     def decode_file( self, data ):
  140.         entry = AppTableEntry( data[:16] )
  141.         entry_position = 16
  142.         entry.decode_data( data[entry.start:entry.start+entry.length] )
  143.         num_entries = entry.index
  144.         self.entries.append( entry )
  145.         print( '  Added App entry # %d' % entry.index )
  146.  
  147.         while( num_entries ):
  148.             num_entries -= 1
  149.             entry = AppTableEntry( data[entry_position:entry_position+16] )
  150.             entry_position += 16
  151.             entry.decode_data( data[entry.start:entry.start+entry.length] )
  152.             self.entries.append( entry )
  153.  
  154.             if( entry.table_crc_calculated == entry.table_crc ):
  155.                 tabcheck = 'pass'
  156.             else:
  157.                 tabcheck = 'BAD, 0x%04X != 0x%04X' % (entry.table_crc_calculated, entry.table_crc)
  158.  
  159.             if( entry.data_crc_calculated == entry.data_crc ):
  160.                 datcheck = 'pass'
  161.             else:
  162.                 datcheck = 'BAD, 0x%04X != 0x%04X' % (entry.data_crc_calculated, entry.data_crc)
  163.  
  164.             print( '  Added App entry # %d, Table Checksum: %s, Data Checksum: %s' % (entry.index, tabcheck, datcheck) )
  165.  
  166.     def encode( self, encode ):
  167.         num_entries = len( self.entries ) - 1
  168.         start = (num_entries + 1) * 16
  169.         idx = None
  170.         header = b''
  171.         body = b''
  172.  
  173.         for e in self.entries:
  174.             thisblock = e.encode_data( encode )
  175.             e.start = start
  176.             e.length = len( thisblock )
  177.             start += e.length
  178.             body += thisblock
  179.  
  180.             if idx is None:
  181.                 e.index = num_entries
  182.                 idx = 0
  183.             else:
  184.                 e.index = idx
  185.                 idx += 1
  186.  
  187.             header += e.encode_entry( encode )
  188.  
  189.         return header + body
  190.  
  191. class FileTypeAudio:
  192.     def __init__( self, data, filetable ):
  193.         self.type = 'f1a'
  194.         self.data = data
  195.  
  196.     def encode( self, encode ):
  197.         #if( encode ):
  198.         return self.data
  199.  
  200.  
  201. class FileTableEntry:
  202.     def __init__( self, data=None ):
  203.         self.table_crc = 0
  204.         self.table_crc_calculated = 0
  205.         self.data_crc = 0
  206.         self.start = 0
  207.         self.length = 0
  208.         self.crc3 = None
  209.         self.index = 0
  210.         self.name = ''
  211.         self.type = ''
  212.         self.data = ''
  213.  
  214.         if( data ):
  215.             self.decode_entry( data )
  216.  
  217.     def decode_entry( self, data ):
  218.         if( len(data) != 32 ):
  219.             raise ValueError('File table data block must be 32 bytes long')
  220.  
  221.         data = decode_block( data )
  222.         self.table_crc_calculated = c16.crc16( data[2:] )
  223.  
  224.         self.table_crc = data[0] << 8 | data[1]
  225.         self.data_crc = data[2] << 8 | data[3]
  226.         self.start = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
  227.         self.length = data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]
  228.         self.index = data[12] << 24 | data[13] << 16 | data[14] << 8 | data[15]
  229.  
  230.         if( self.length > 0xFFFFF ):
  231.             self.crc3 = self.length
  232.             self.length = self.index * 0x20
  233.             self.type = 'header'
  234.             for c in data[16:]:
  235.                 self.name += chr(c)
  236.         else:
  237.             for c in data[16:]:
  238.                 if( c == 0xFF ):
  239.                     continue
  240.                 if( c == 0x00 ):
  241.                     break
  242.                 self.name += chr(c)
  243.  
  244.             self.type = self.name.split( '.' )[-1].lower()
  245.  
  246.     def encode_entry( self, encode ):
  247.         block = self.data_crc.to_bytes(2, 'big') + self.start.to_bytes(4, 'big')
  248.  
  249.         if self.crc3 is None:
  250.             block += self.length.to_bytes(4, 'big')
  251.         else:
  252.             block += self.crc3.to_bytes(4, 'big')
  253.  
  254.         block += self.index.to_bytes(4, 'big')
  255.  
  256.         name = self.name
  257.         if( len( name ) < 16 ):
  258.             name += chr( 0 )
  259.         if( len( name ) < 16 ):
  260.             name += chr( 0xFF ) * (16 - len( name ))
  261.  
  262.         #for c in name:
  263.         #    block += ord( c ).to_bytes(1, 'big')
  264.         block += bytes( [ ord( c ) for c in name ] )
  265.  
  266.         if self.crc3 is None:
  267.             crc = 0
  268.         else:
  269.             crc = c16.crc16( block )
  270.  
  271.         block = crc.to_bytes(2, 'big') + block
  272.  
  273.         if( encode ):
  274.             return bytes( decode_block( block ) )
  275.  
  276.         return block
  277.  
  278.     def decode_data( self, data ):
  279.         # .bin and .sys files are not encoded
  280.         if( self.type == 'header' or self.type == 'bin' or self.type == 'sys' ):
  281.             self.data = FileTypeRaw( data, self )
  282.             return
  283.  
  284.         if( self.type == 'app' ):
  285.             self.data = FileTypeApp( data, self )
  286.             return
  287.  
  288.         if( self.type == 'f1a' ):
  289.             self.data = FileTypeAudio( data, self )
  290.             return
  291.  
  292.         raise NotImplementedError( 'Data handler for file type ' + self.type + ' not implemented!' )
  293.  
  294.     def encode_data( self, encode ):
  295.         return self.data.encode( encode )
  296.  
  297. if __name__ == '__main__':
  298.     infile = outfile = None
  299.     c16 = crc16()
  300.  
  301.     if( len(sys.argv) >= 2 ):
  302.         infile = sys.argv[1]
  303.  
  304.     if( len(sys.argv) >= 3 ):
  305.         outfile = sys.argv[2]
  306.  
  307.     if( infile is None ):
  308.         infile = input( 'Enter input file name: ' )
  309.  
  310.     while( not os.path.exists( infile ) ):
  311.         print( 'Input file not found!' )
  312.         infile = input( 'Enter input file name: ' )
  313.  
  314.     if( outfile is None ):
  315.         outfile = input( 'Enter output file name: ' )
  316.  
  317.     haveoutfile = os.path.exists( outfile )
  318.  
  319.     while( haveoutfile ):
  320.         outfile = input( 'Output file already exists, type Y to overwrite or enter a new file name: ' )
  321.  
  322.         if( len( outfile ) < 3 and outfile == 'Y' ):
  323.             haveoutfile = False
  324.         else:
  325.             haveoutfile = os.path.exists( outfile )
  326.  
  327.  
  328.     with open( infile, 'rb' ) as ifp:
  329.         good = True
  330.         fileheader = FileTableEntry( ifp.read( 0x20 ) )
  331.         files = [ ]
  332.  
  333.         block = ifp.read( fileheader.length )
  334.  
  335.         if( len( block ) != (fileheader.length) ):
  336.             raise EOFError( 'Input file too small!' )
  337.  
  338.         if( fileheader.table_crc != fileheader.table_crc_calculated ):
  339.             print( 'Header Checksum: BAD, 0x%04X != 0x%04X' % (fileheader.table_crc_calculated, fileheader.table_crc) )
  340.             good = False
  341.         else:
  342.             print( 'Header Checksum: pass' )
  343.  
  344.         tablecrc = c16.crc16( block )
  345.         if( tablecrc != fileheader.data_crc and tablecrc != fileheader.crc3 >> 16 ):
  346.             print( 'Table Checksum: BAD, 0x%04X != 0x%04X' % (tablecrc, fileheader.data_crc) )
  347.             good = False
  348.         else:
  349.             print( 'Table Checksum: pass' )
  350.  
  351.         while( len( block ) > 0 ):
  352.             thisfile = FileTableEntry( block[:0x20] )
  353.             fname = 'File # %d "%s"' % (thisfile.index, thisfile.name)
  354.             block = block[0x20:]
  355.             ifp.seek( thisfile.start )
  356.             data = ifp.read( thisfile.length )
  357.  
  358.             if( len( data ) != (thisfile.length) ):
  359.                 print( fname + ' data too small!' )
  360.                 break
  361.  
  362.             datacrc = c16.crc16( data )
  363.  
  364.             if( datacrc != thisfile.data_crc ):
  365.                 print( fname + ' Checksum: BAD, 0x%04X != 0x%04X [data length: %04X]' % (datacrc, thisfile.data_crc, thisfile.length) )
  366.                 good = False
  367.             else:
  368.                 print( fname + ' Checksum: pass' )
  369.  
  370.             thisfile.decode_data( data )
  371.             files.append( thisfile )
  372.  
  373.  
  374.  
  375.     with open( outfile, 'wb' ) as ofp:
  376.         encode = True #False
  377.  
  378.         table_clear = b''
  379.         table_enc = b''
  380.  
  381.         for thisfile in files:
  382.             table_clear += thisfile.encode_entry( False )
  383.             table_enc += thisfile.encode_entry( True )
  384.  
  385.         fileheader.data_crc = c16.crc16( table_enc )
  386.         fileheader.crc3 = (fileheader.data_crc << 16) | 0x4452 # "DR"
  387.  
  388.         ofp.write( fileheader.encode_entry( encode ) )
  389.  
  390.         if( encode ):
  391.             ofp.write( table_enc )
  392.         else:
  393.             ofp.write( table_clear )
  394.  
  395.         curpos = ofp.tell()
  396.         odd = (fileheader.start % 0x2000) + (0x2000 - curpos)
  397.         pad = bytes( [ 0xFF ] * odd )
  398.         ofp.write( pad )
  399.         curpos += odd
  400.         pad = bytes( [ 0xFF ] * 0x2000 )
  401.  
  402.         while curpos < fileheader.start:
  403.             ofp.write( pad )
  404.             curpos += 0x2000
  405.  
  406.  
  407.  
  408.         for thisfile in files:
  409.             if( thisfile.start < ((len(files) + 1) * 0x20) ):
  410.                 print( 'Bad start position!' )
  411.             curpos = ofp.tell()
  412.  
  413.             # ugh, the gap between the last .f1a file and map_table.sys is padded with 0x00's unlike the rest of the file
  414.             if( thisfile.name == 'map_table.sys' and curpos < thisfile.start and (thisfile.start - curpos) < 0x200 ):
  415.                 pad = bytes( [ 0x00 ] * (thisfile.start - curpos) )
  416.                 ofp.write( pad )
  417.  
  418.             ofp.seek( thisfile.start )
  419.             ofp.write( thisfile.encode_data( encode ) )
  420.  
  421.  
  422.         if( encode ):
  423.             curpos = ofp.tell()
  424.             left = 0x200000 - curpos
  425.             if( left > 0 ):
  426.                 odd = left % 0x2000
  427.                 left -= odd
  428.                 pad = bytes( [ 0xFF ] * odd )
  429.                 ofp.write( pad )
  430.                 pad = bytes( [ 0xFF ] * 0x2000 )
  431.  
  432.                 while( left > 0 ):
  433.                     ofp.write( pad )
  434.                     left -= 0x2000
  435.  
  436.  
  437.  
  438.  
RAW Paste Data