shornby

LA Noire file parser (COPY).py

Jun 16th, 2013
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import sys, os, errno
  2. import array
  3. from struct import *
  4. import zlib
  5.  
  6. def align(x, a):
  7.     return (x + (a - 1)) & ~(a - 1);
  8.  
  9. def mkdirSafe(dirs):
  10.     try:
  11.         os.makedirs(dirs)
  12.     except OSError, e:
  13.         if e.errno != errno.EEXIST:
  14.             raise
  15.  
  16. class BigArchive:
  17.     class Entry:
  18.         def __init__(self):
  19.             self.hash = None
  20.             self.offset = None
  21.             self.size1 = None
  22.             self.size2 = None
  23.             self.size3 = None
  24.  
  25.     class Chunk:
  26.         def __init__(self):
  27.             self.offset = None
  28.             self.size = None
  29.             self.flags = None
  30.             self.size_coeff = None
  31.  
  32.     def __init__(self, file, endianness):
  33.         self.endianness = endianness
  34.         self.file = file
  35.         self.file_size = None
  36.         self.entries = None
  37.  
  38.         current_pos = self.file.tell()
  39.         self.file.seek(0, os.SEEK_END)
  40.         self.file_size = self.file.tell()
  41.         self.file.seek(current_pos)
  42.         self.file.seek(self.file_size - calcsize('<I'))
  43.  
  44.         self.file_table_offset = self.file_size - unpack('<I', self.file.read(calcsize('<I')))[0];
  45.         self.file.seek(self.file_table_offset)
  46.  
  47.         (archive_type, num_entries) = unpack('<2I', self.file.read(calcsize('<2I')))
  48.         if archive_type != 3:
  49.             print 'Unsupported archive type %d' % archive_type
  50.             return
  51.  
  52.         self.entries = []
  53.         for i in xrange(num_entries):
  54.             entry = self.Entry()
  55.  
  56.             (entry.hash, entry_offset, entry.size1, entry.size2, entry.size3) = unpack(self.endianness + '5I', self.file.read(calcsize(self.endianness + '5I')))
  57.             entry_offset_lo = entry_offset << 4
  58.             entry_offset_hi = entry_offset >> 28
  59.             entry.offset = entry_offset_lo
  60.             # size3 for compressed segs
  61.  
  62.             self.entries.append(entry)
  63.  
  64.     def dumpEntry(self, entry, data):
  65.         with open('entries/0x%08x' % entry.hash, 'wb') as out_file:
  66.             out_file.write(data)
  67.  
  68.     def processSingle(self, entry):
  69.         size = entry.size3
  70.         if size == 0:
  71.             size = entry.size1
  72.         self.file.seek(entry.offset)
  73.         data = self.file.read(size)
  74.         self.dumpEntry(entry, data)
  75.  
  76.     def processMulti(self, entry):
  77.         self.file.seek(entry.offset)
  78.         (magic, type, num_chunks, u0, u1, u2, u3) = unpack(self.endianness + 'I2H4B', self.file.read(calcsize(self.endianness + 'I2H4B')))
  79.         print '\tType: %d' % type
  80.         print '\tNum chunks: %d' % num_chunks
  81.         print '\tUnknown: %d %d %d %d' % (u0, u1, u2, u3)
  82.         data_offset = align(self.file.tell() + u0 * calcsize(self.endianness + 'I') + num_chunks * calcsize(self.endianness + '2H'), 16)
  83.  
  84.         uobjs = []
  85.         for i in xrange(u0):
  86.             uobj = unpack(self.endianness + 'I', self.file.read(calcsize(self.endianness + 'I')))[0]
  87.             uobjs.append(uobj)
  88.  
  89.         chunks = []
  90.         for i in xrange(num_chunks):
  91.             chunk = self.Chunk()
  92.  
  93.             (chunk.size, chunk.flags, chunk.size_coeff) = unpack(self.endianness + 'H2B', self.file.read(calcsize(self.endianness + 'H2B')))
  94.  
  95.             chunk.offset = data_offset
  96.             chunk.size += 0x10000 * chunk.size_coeff
  97.             data_offset += chunk.size
  98.             chunks.append(chunk)
  99.  
  100.         data = ''
  101.  
  102.         for i in xrange(len(uobjs)):
  103.             uobj = uobjs[i]
  104.  
  105.             print '\tObject %d:' % i
  106.             print '\t\tData: %d' % uobj
  107.         print ''
  108.  
  109.         for i in xrange(len(chunks)):
  110.             chunk = chunks[i]
  111.  
  112.             print '\tChunk %d:' % i
  113.             print '\t\tOffset: %d' % chunk.offset
  114.             print '\t\tSize: %d' % chunk.size
  115.             print '\t\tFlags: 0x%02x' % (chunk.flags)
  116.             print '\t\tSize coeff: %d' % (chunk.size_coeff)
  117.  
  118.             self.file.seek(chunk.offset)
  119.             if chunk.flags & 0x10:
  120.                 data += zlib.decompress(self.file.read(chunk.size), -15)
  121.             else:
  122.                 data += self.file.read(chunk.size)
  123.  
  124.         self.dumpEntry(entry, data)
  125.  
  126.     def unpack(self):
  127.         num_segments = 0
  128.         with open('entries.txt', 'w') as entries_list_file:
  129.             if not self.entries:
  130.                 offset = 0
  131.                
  132.                 while offset < self.file_table_offset:
  133.                     self.file.seek(offset)
  134.  
  135.                     magic = unpack(self.endianness + 'I', self.file.read(calcsize(self.endianness + 'I')))[0]
  136.                     self.file.seek(-calcsize(self.endianness + 'I'), os.SEEK_CUR)
  137.                     if magic == unpack_from('>I', 'segs')[0]:
  138.                         (magic, type, num_chunks, u0, u1, u2, u3) = unpack(self.endianness + 'I2H4B', self.file.read(calcsize(self.endianness + 'I2H4B')))
  139.                         print '\tType: %d' % type
  140.                         print '\tNum chunks: %d' % num_chunks
  141.                         print '\tUnknown: %d %d %d %d' % (u0, u1, u2, u3)
  142.                         data_offset = align(self.file.tell() + u0 * calcsize(self.endianness + 'I') + num_chunks * calcsize(self.endianness + '2H'), 16)
  143.  
  144.                         uobjs = []
  145.                         for i in xrange(u0):
  146.                             uobj = unpack(self.endianness + 'I', self.file.read(calcsize(self.endianness + 'I')))[0]
  147.                             uobjs.append(uobj)
  148.  
  149.                         chunks = []
  150.                         chunks_total_size = 0
  151.                         for i in xrange(num_chunks):
  152.                             chunk = self.Chunk()
  153.  
  154.                             (chunk.size, chunk.flags, chunk.size_coeff) = unpack(self.endianness + 'H2B', self.file.read(calcsize(self.endianness + 'H2B')))
  155.  
  156.                             chunk.offset = data_offset
  157.                             chunk.size += 0x10000 * chunk.size_coeff
  158.                             chunks_total_size += chunk.size
  159.                             data_offset += chunk.size
  160.                             chunks.append(chunk)
  161.  
  162.                         data = ''
  163.  
  164.                         for i in xrange(len(uobjs)):
  165.                             uobj = uobjs[i]
  166.  
  167.                             print '\tObject %d:' % i
  168.                             print '\t\tData: %d' % uobj
  169.                         print ''
  170.  
  171.                         for i in xrange(len(chunks)):
  172.                             chunk = chunks[i]
  173.  
  174.                             print '\tChunk %d:' % i
  175.                             print '\t\tOffset: %d' % chunk.offset
  176.                             print '\t\tSize: %d' % chunk.size
  177.                             print '\t\tFlags: 0x%02x' % (chunk.flags)
  178.                             print '\t\tSize coeff: %d' % (chunk.size_coeff)
  179.  
  180.                             self.file.seek(chunk.offset)
  181.                             if chunk.flags & 0x10:
  182.                                 data += zlib.decompress(self.file.read(chunk.size), -15)
  183.                             else:
  184.                                 data += self.file.read(chunk.size)
  185.                         print 'Total size: %d' % chunks_total_size
  186.  
  187.                         with open('segments/%06d' % num_segments, 'wb') as fout:
  188.                             fout.write(data)
  189.  
  190.                         while self.file.tell() + 16 < self.file_table_offset:
  191.                             padding = self.file.read(16)
  192.                             if padding != 'X' * 16 and padding != '\x00' * 16:
  193.                                 self.file.seek(-16, os.SEEK_CUR)
  194.                                 break
  195.                         offset = self.file.tell()
  196.  
  197.                         print 'seg %d: %d -> %d ::: %3d' % (num_segments, data_offset, offset, offset - data_offset)
  198.                         num_segments += 1
  199.                     else:
  200.                         print 'DAMN!!!!!!!!!!!!!'
  201.                 print ''
  202.  
  203.             for i in xrange(len(self.entries)):
  204.                 entry = self.entries[i]
  205.  
  206.                 entries_list_file.write('0x%08x\n' % entry.hash)
  207.                
  208.                 self.file.seek(entry.offset)
  209.                 magic = unpack(self.endianness + 'I', self.file.read(calcsize(self.endianness + 'I')))[0]
  210.                 self.file.seek(-calcsize(self.endianness + 'I'), os.SEEK_CUR)
  211.                 if magic == unpack_from('>I', 'segs')[0]:
  212.                     print 'processing multi...'
  213.                     print '\tHash: 0x%08x' % entry.hash
  214.                     print '\tOffset: %d' % entry.offset
  215.                     print '\tSize1: %d' % entry.size1
  216.                     print '\tSize2: %d' % entry.size2
  217.                     print '\tSize3: %d' % entry.size3
  218.                     self.processMulti(entry)
  219.                 else:
  220.                     print 'processing single...'
  221.                     print '\tHash: 0x%08x' % entry.hash
  222.                     print '\tOffset: %d' % entry.offset
  223.                     print '\tSize1: %d' % entry.size1
  224.                     print '\tSize2: %d' % entry.size2
  225.                     print '\tSize3: %d' % entry.size3
  226.                     self.processSingle(entry)
  227.                 print ''
  228.  
  229. def processFile(file_name):
  230.     endianness = None
  231.     if file_name.endswith('.pc'):
  232.         endianness = '<'
  233.     elif file_name.endswith('.ps3'):
  234.         endianness = '>'
  235.     elif file_name.endswith('.360'):
  236.         print 'Xbox 360 format is not supported for now'
  237.         return
  238.     else:
  239.         print 'Unknown format'
  240.         return
  241.  
  242.     with open(file_name, 'rb') as file:
  243.         try:
  244.             arc = BigArchive(file, endianness)
  245.             arc.unpack()
  246.         except EOFError:
  247.             print 'Failed open file'
  248.             return
  249.  
  250. mkdirSafe('entries')
  251. mkdirSafe('segments')
  252.  
  253. map(lambda x: processFile(x) if os.path.exists(x) else None, sys.argv[1:])
Add Comment
Please, Sign In to add comment