def __processBlockData(self, f): """ Iterates over the list of block headers and parses the data for each block. """ dna = self.__dna types = dna.getTypes() if dna else None structs = dna.getStructs() if dna else None for block in self.__blockHeaders: if block['processed']: continue # already processed code = block['blockCode'] dataLength = block['blockLength'] f.seek(block['filePos']) if code == 'REND': # process the abbreviated render data for idx in range(0, block['numberOfStructs']): sframe = getInt(f.read(4)) # start frame number eframe = getInt(f.read(4)) # end frame number scene = (f.read(64)).decode().rstrip('\0') rd = RenderData() rd.startFrame = sframe rd.endFrame = eframe rd.sceneName = scene self.__renderData.append(rd) block['processed'] = True elif code == 'TEST': # The block data is a thumbnail image in RGBA format # Data starts with two integers, the width and height of the image width = getInt(f.read(4)) height = getInt(f.read(4)) daimg = Image.frombytes('RGBA', (width, height), f.read(dataLength - 8)) self.__thumbnailImage = daimg.transpose( method=Image.FLIP_TOP_BOTTOM) block['processed'] = True else: scode = block['structCode'] daspecs = block['memberSpecs'] if (scode == 0 and len(daspecs) == 0) or not dna: continue # can't process else: dastruct = structs[scode] stype = types[dastruct[0]] slist = [] for idx in range(0, block['numberOfStructs']): slist.append( self.__buildStruct(stype, block, mspecs=daspecs)) block['structData'] = slist block['processed'] = True
def __getBlockHeader(self, f): """ Parses block headers and saves data to a dictionary. Dictionary keys: blockCode a string of 2 or 4 characters indicating the block type blockLength length in bytes of the data following the block header oldPointer memory address of block when it was saved structCode index into the array of structure definitions read from the structure DNA. The data in the block conforms to this structure. numberOfStructs the data consists of this number of consecutive structs filePos file offset to block's data structData list of Struct objects that comprise the block's data references pointers that point to this block memberSpecs for ad-hoc structures (i.e. not in the structure DNA), a list of (type, name) tuples for the structure members """ pointerSize = self.__fileHeader['pointerSize'] # first 4 bytes are a block type code raw = f.read(4) if len(raw) != 4: return None code = raw.decode().rstrip('\0') # next 4 bytes are the length of data after the block raw = f.read(4) if len(raw) != 4: return None length = getInt(raw) # next 4 or 8 bytes are a pointer raw = f.read(pointerSize) if len(raw) != pointerSize: return None oldPointer = getUint(raw) # next 4 bytes are a structure code - need structure DNA to interpret raw = f.read(4) if len(raw) != 4: return None structCode = getInt(raw) # next 4 bytes are the number of structures in this block raw = f.read(4) if len(raw) != 4: return None numStructs = getInt(raw) # quickrefs are strings consisting of the struct name, a "|" char, and the # member type and member name separated by a space. # referringMembers is a list of StructureMember objects. emptyrefs = {'quickRefs': set(), 'referringMembers': []} return { 'blockCode': code, 'blockLength': length, 'oldPointer': oldPointer, 'structCode': structCode, 'numberOfStructs': numStructs, 'references': emptyrefs, 'memberSpecs': [] }
def __getStringBlock(self, f, type, postProc=None): """ Loads a block of null-terminated strings into an array The block consists of a 4-character type string, e.g. NAME, followed by a 4-byte integer representing the count of strings, followed by the strings themselves. Arguments are the file stream and the type string. The function returns the strings in a list, or None if there was a problem. There is also an optional postProc function argument. This is a function which takes a string and returns an altered string. """ # first 4 bytes must be the type if check4(f, type) == None: return None # next 4 bytes are an integer representing the total number of strings # of the given type typesLen = getInt(f.read(4)) # fetch all the name strings and save allTypes = [] for runningLen in range(0, typesLen): theString = getString(f) if postProc != None: theString = postProc(theString) allTypes.append(theString) return allTypes
def processDNA(self): # first 4 bytes must be string "SDNA" if check4(self.__f, 'SDNA') == None: return # get the NAME strings self.__allNames = self.__getStringBlock(self.__f, 'NAME') if self.__allNames == None or len(self.__allNames) == 0: return # seek to next 4-byte aligned position padUp4(self.__f) # get the TYPE strings self.__allTypes = self.__getStringBlock( self.__f, 'TYPE', self.__DNA_struct_rename_legacy_hack_static_from_alias) if self.__allTypes == None or len(self.__allTypes) == 0: return padUp4(self.__f) # next 4 bytes must be "TLEN" if check4(self.__f, 'TLEN') == None: return # following data is a sequence of 2-byte integers representing the # length (in bytes) of each type in __allTypes, in the same order. for idx in range(0, len(self.__allTypes)): theLen = getInt(self.__f.read(2)) self.__allTypeLengths.append(theLen) padUp4(self.__f) # get the structure definitions self.__allStructs = self.__getStructures(self.__f)
def __getStructures(self, f): """ getStructures fetches all the structure arrays and returns them as a list of lists, where each element list is an encoded structure definition. Input structure definition format: Each structure definition is a variable length array of 2-byte integers. index meaning ----- ------- 0 struct type number, i.e. index into __allTypes array 1 number of members in the structure 2 type number of first member 3 name number of first member, i.e. index into allnames array ... repeats for each member We will save these as a list where the first element is the structure type code, and the second element is a list of tuples, where each tuple is a type/name code pair. """ # first 4 bytes of structs section must be "STRC" if check4(f, 'STRC') == None: return None # read the number of structure definitions numStructs = getInt(f.read(4)) theStructs = [] typeNames = self.getTypes() for structIdx in range(0, numStructs): curStruct = [] structType = getInt(f.read(2)) curStruct.append(structType) numMembers = getInt(f.read(2)) curMembers = [] for memberIdx in range(0, numMembers): typeCode = getInt(f.read(2)) nameCode = getInt(f.read(2)) curMembers.append((typeCode, nameCode)) curStruct.append(curMembers) if len(curStruct) > 0: theStructs.append(curStruct) self.__structCodesByType[typeNames[structType]] = structIdx if len(theStructs) > 0: return theStructs else: return None
def __getSingleValue(self, mtype, name, isSimpleType, dimensions, isPointer, smember): """ Creates a simple value from raw data """ f = self.__f # file object if isPointer: # make a pointer hdr = self.getFileHeader() psize = hdr['pointerSize'] ptr = Pointer(getUint(f.read(psize))) # check for blocks referred to by this pointer hdrs = self.getHeadersByAddress() b = hdrs.get(ptr) if b: stype = smember.parentStruct.stype ptr.pointsTo(b) # add a reference to this pointer to the destination block b['references']['quickRefs'].add(stype + '|' + mtype + ' ' + name) b['references']['referringMembers'].append(smember) return ptr if not isSimpleType: # find enclosing block parent = smember.parentStruct while parent: if parent.block: break parent = parent.parentStruct return self.__buildStruct( mtype, parent.block, name, parentMember=smember) # value is another struct if mtype == 'char': numDim = len(dimensions) if numDim == 0: return getInt(f.read(1)) else: maxlen = dimensions[0] raw = f.read(maxlen) rs = raw nulidx = raw.find(b'\x00') if nulidx >= 0: rs = raw[0:nulidx] try: tstring = rs.decode() except UnicodeDecodeError: return raw return tstring elif mtype == 'uchar': return getUint(f.read(1)) elif mtype == 'short': return getInt(f.read(2)) elif mtype == 'ushort': return getUint(f.read(2)) elif mtype == 'int' or type == 'long': return getInt(f.read(4)) elif mtype == 'ulong': return getUint(f.read(4)) elif mtype == 'int64_t': return getInt(f.read(8)) elif mtype == 'uint64_t': return getInt(f.read(8)) elif mtype == 'float': return getFloat(f.read(4)) elif mtype == 'double': return getDouble(f.read(8)) else: return None