def open(self, file_path): with open(file_path, 'rb') as f: file_size = common.get_file_size(f) # Read magic header magic_number = f.read(4) if magic_number != 'BIND': raise Exception('Not a BIND file! Missing BIND header id.') # Read "size" byte size # This determines what's the "size" of the size variable itself self.size_byte_size = common.read_uint16(f) if self.size_byte_size not in (1, 2, 4): raise Exception('Illegal BIND size byte size: %s' % self.index_size) # Read number of entries number_of_entries = common.read_uint16(f) # Read block size self.block_size = common.read_uint32(f) # Read header size header_size = common.read_uint32(f) # The header size is: # (16 + number of entries * self.size_byte_size) aligned to self.block_size # Load the entry sizes processed_entries = [] previous_entry_end = header_size for i in xrange(0, number_of_entries): # Calculate the offset of this entry based on the end of the previous entry entry_offset = previous_entry_end # Read the entry size entry_size = 0 if self.size_byte_size == 1: entry_size = common.read_uint8(f) elif self.size_byte_size == 2: entry_size = common.read_uint16(f) elif self.size_byte_size == 4: entry_size = common.read_uint32(f) # Calculate how much memory this entry takes entry_padded_size = common.align_size(entry_size, self.block_size) # Update previous_entry_end for the next iteration previous_entry_end += entry_padded_size # Add the entry to processed entries processed_entries.append((entry_offset, entry_size)) # Read entries for (entry_offset, entry_size) in processed_entries: f.seek(entry_offset) content = f.read(entry_size) new_entry = self.add_entry(content)
def open(self, file_path): with open(file_path, 'rb') as f: magic_number = f.read(4).decode('ascii', 'ignore') if magic_number != 'HGAR': raise Exception('Not an HGAR file!') self.version = common.read_uint16(f) if self.version not in (1, 3): raise Exception('Unknown HGAR version: %s' % self.version) number_of_files = common.read_uint16(f) file_header_offsets = [0] * number_of_files for i in range(0, number_of_files): file_header_offsets[i] = common.read_uint32(f) file_unknowns = [(None, None)] * number_of_files file_long_names = [None] * number_of_files if self.version == 3: for i in range(0, number_of_files): file_unknowns[i] = (common.read_uint32(f), common.read_uint32(f)) for i in range(0, number_of_files): # Read file number file_number = common.read_uint32(f) # Read name until null-terminator (but aligned to 32-bit boundaries) long_name = b'' while True: long_name += f.read(4) if long_name[-1] == 0: break file_long_names[i] = long_name if file_number != i: print('\tWarning: File "%s" is stored as file number %s, but should be %s' % (long_name, file_number, i)) for i in range(0, number_of_files): f.seek(file_header_offsets[i]) short_name = f.read(0xC) reformatted_short_name = short_name[0:-4].rstrip() + short_name[-4:].rstrip() encoded_identifier = common.read_uint32(f) file_size = common.read_uint32(f) new_file = self.add_file(file_long_names[i], reformatted_short_name, file_size) new_file.encoded_identifier = encoded_identifier new_file.unknown_first = file_unknowns[i][0] new_file.unknown_last = file_unknowns[i][1] new_file.content = f.read(file_size) # The new file object will generate the short name and long name # upon creation, BUT overwrite these since we're opening # and know the exact values (instead of creating from scratch) new_file.short_name = reformatted_short_name new_file.long_name = file_long_names[i] # This must be done after the exact file count is known self.decode_identifiers()
def open(self, file_path): with open(file_path, 'rb') as f: file_size = common.get_file_size(f) # Read magic header magic_number = f.read(4) if magic_number != 'HGPT': raise Exception('Not an HGPT file! Missing HGPT header id.') # Read pp offset pp_offset = common.read_uint16(f) if pp_offset < 0x10: raise Exception( 'PP offset less than 0x10, PS2 variant not supported!') # Read if it has an extended header has_extended_header = common.read_uint16(f) if has_extended_header not in (0, 1): raise Exception('Unknown has_extended_header value: %s' % has_extended_header) self.has_extended_header = has_extended_header = ( has_extended_header == 1) # Read number of divisions number_of_divisions = common.read_uint16(f) # Read unknowns unknown_one = common.read_uint16(f) if unknown_one != 0x0001: raise Exception('First unknown is not 0x0001: %08X' % unknown_one) # ff ff ff ff in pictures with extended header # 00 00 00 00 in pictures w/o extended header unknown_two = common.read_uint32(f) # Load divisions if has_extended_header: # Read number of divisions (again) number_of_divisions_repeat = common.read_uint16(f) if number_of_divisions != number_of_divisions_repeat: raise Exception( 'Number of divisions and its repeat don\'t match: %s != %s' % (number_of_divisions, number_of_divisions_repeat)) # Read unknown # 0x0013 is the most common # 0x0014 is the other occurence unknown_three = common.read_uint16(f) if unknown_three != 0x0013: print '# Warning: UnknownThree (0x%X) != 0x0013' % unknown_three # Read division name self.division_name = (f.read(8) + '\0' * 8)[0:8] # Read divisions for i in xrange(0, number_of_divisions): division_start_x = common.read_uint16(f) division_start_y = common.read_uint16(f) division_width = common.read_uint16(f) division_height = common.read_uint16(f) self.divisions.append((division_start_x, division_start_y, division_width, division_height)) # Calculate and skip zero padding divisions_size = 12 + 8 * number_of_divisions divisions_padded_size = common.align_size(divisions_size, 16) divisions_padding = divisions_padded_size - divisions_size f.seek(divisions_padding, os.SEEK_CUR) # Check that it's the correct pp_offset if f.tell() != pp_offset: raise Exception('Incorrect pp offset') # Read pp header pp_header = common.read_uint32(f) if pp_header & 0x0000FFFF != 0x00007070: raise Exception('Missing pp header! %08X' % pp_header) # Decipher pp format pp_format = (pp_header >> 16) & 0xFFFF if pp_format not in (0x13, 0x14, 0x8800): raise Exception('PP format (0x%X) is unknown' % pp_format) # Calculate values that depend on the pp format bytes_per_pixel = 1 bytes_per_pixel_ppd_size = bytes_per_pixel tile_width = 16 if pp_format == 0x8800: bytes_per_pixel = 4 bytes_per_pixel_ppd_size = 1 tile_width = 4 elif pp_format == 0x13: bytes_per_pixel = 1 bytes_per_pixel_ppd_size = bytes_per_pixel tile_width = 16 elif pp_format == 0x14: bytes_per_pixel = 0.5 bytes_per_pixel_ppd_size = bytes_per_pixel tile_width = 32 # Read picture display dimensions self.width = pp_display_width = common.read_uint16(f) self.height = pp_display_height = common.read_uint16(f) # Skip zero padding f.seek(2 * 4, os.SEEK_CUR) # Read ppd header ppd_header = common.read_uint32(f) if ppd_header & 0x00FFFFFF != 0x00647070: raise Exception('Missing ppd header!') # Decipher ppd format ppd_format = (ppd_header >> 24) & 0xFF if ppd_format != (pp_format & 0xFF): raise Exception( 'PPD format (0x%X) does not match PP format (0x%X)' % (ppd_format, pp_format & 0xFF)) # Read ppd display resolution ppd_display_width = common.read_uint16(f) ppd_display_height = common.read_uint16(f) if pp_display_width != ppd_display_width: raise Exception( 'PP display width (%s) != PPD display width (%s)' % (pp_display_width, ppd_display_width)) if pp_display_height != ppd_display_height: raise Exception( 'PP display height (%s) != PPD display height (%s)' % (pp_display_height, ppd_display_height)) # Skip zero padding f.seek(4, os.SEEK_CUR) # Read ppd sixteenths resolution ppd_sixteenths_width = common.read_uint16(f) ppd_sixteenths_height = common.read_uint16(f) # Calculate ppd sixteenths resolution (using the pp_display resolution) calculated_sixteenths_width = common.align_size( pp_display_width, 16) calculated_sixteenths_height = common.align_size( pp_display_height, 8) if (calculated_sixteenths_width != ppd_sixteenths_width) or ( calculated_sixteenths_height != ppd_sixteenths_height): raise Exception( 'PPD sixteenths resolution (%s x %s) doesn\'t match the calculated eights resolution (%s x %s)' % (ppd_sixteenths_width, ppd_sixteenths_height, calculated_sixteenths_width, calculated_sixteenths_height)) # Calculate storage resolution (using the pp_display resolution) # This is the resolution that ties in with the ppd_size calculated_storage_width = common.align_size( pp_display_width, tile_width) calculated_storage_height = common.align_size(pp_display_height, 8) number_of_pixels = calculated_storage_width * calculated_storage_height # Read ppd_size which is the size of the ppd header + number of pixels # The ppd header which is 0x20 bytes in size ppd_size = common.read_uint32(f) calculated_ppd_size = int( number_of_pixels * bytes_per_pixel_ppd_size) + 0x20 if (calculated_ppd_size != ppd_size): raise Exception( 'PPD size %s does not match the calculated ppd size %s' % (ppd_size, calculated_ppd_size)) # Calculate the number of bytes number_of_bytes = int(number_of_pixels * bytes_per_pixel) # Skip padding f.seek(3 * 4, os.SEEK_CUR) # Read the tiled image data # The image data is stored in a scrambled tiled format, so we'll have to reprocess it tiled_image_data = [0] * number_of_pixels cache_last_pixel = None for i in xrange(0, number_of_pixels): if number_of_bytes <= 0: break if pp_format == 0x13: tiled_image_data[i] = common.read_uint8(f) number_of_bytes -= 1 elif pp_format == 0x14: if (i & 1) == 0: # Even read cache_last_pixel = common.read_uint8(f) else: # Odd read number_of_bytes -= 1 tiled_image_data[i] = cache_last_pixel & 0xF cache_last_pixel = cache_last_pixel >> 4 elif pp_format == 0x8800: # Read full RGBA tiled_image_data[i] = (common.read_uint8(f), common.read_uint8(f), common.read_uint8(f), decode_alpha(common.read_uint8(f))) number_of_bytes -= 4 # Skip unread bytes f.seek(number_of_bytes, os.SEEK_CUR) # Un-tile and store the information as the content self.content = [0] * (pp_display_width * pp_display_height) tile_height = 8 tile_size = tile_width * tile_height tile_row = tile_size * int(calculated_storage_width / tile_width) for y in xrange(0, pp_display_height): for x in xrange(0, pp_display_width): tile_y = int(y / tile_height) tile_x = int(x / tile_width) tile_sub_y = y % tile_height tile_sub_x = x % tile_width self.content[y * pp_display_width + x] = tiled_image_data[tile_y * tile_row + tile_x * tile_size + tile_sub_y * tile_width + tile_sub_x] # Check if optional ppc header exists if pp_format == 0x8800: self.palette = [] else: # Read ppc header ppc_header = common.read_uint32(f) if ppc_header & 0xFFFFFFFF != 0x00637070: raise Exception('Missing ppc header!') # Skip zero padding f.seek(2, os.SEEK_CUR) # Read the number of palette entries # It needs to be multiplied by 8 to get the correct total palette_total = common.read_uint16(f) * 8 # Skip zero padding f.seek(2 * 4, os.SEEK_CUR) # Read palette self.palette = [(common.read_uint8(f), common.read_uint8(f), common.read_uint8(f), decode_alpha(common.read_uint8(f))) for i in xrange(0, palette_total)]
def open(self, file_path): with open(file_path, 'rb') as f: file_size = common.get_file_size(f) # Read magic header magic_number = f.read(4).decode('ascii', 'ignore') if magic_number != '.EVS': raise Exception('Not an EVS file!') # Read the number of entries number_of_entries = common.read_uint32(f) # Read entry offsets entry_offsets = [0] * number_of_entries for i in range(0, number_of_entries): entry_offsets[i] = common.read_uint32(f) # Loop through entries for i in range(0, number_of_entries): f.seek(entry_offsets[i]) # Read entry type entry_type = common.read_uint16(f) # Read entry size entry_size = common.read_uint16(f) # Read entry number_of_parameters = get_number_of_parameters(entry_type) has_content_section = entry_type in HAS_CONTENT_SECTION if number_of_parameters is None: # Commands that I've yet to document the parameter count for raise Exception( 'Unknown number of parameters for entry type: 0x%X, size: %s' % (entry_type, entry_size)) # Read the parameters entry_parameters = [] for j in range(0, number_of_parameters): entry_parameters.append(common.read_uint32(f)) # Whatever remains after the parameters is the content parameter_size = 4 * number_of_parameters remaining_bytes = entry_size - parameter_size if remaining_bytes < 0: raise Exception( 'Number of parameters overshoots total entry size; entry type: 0x%X, size: %s' % (entry_type, entry_size)) if has_content_section: raw_entry_content = f.read(remaining_bytes) # Convert encoding entry_content = common.from_eva_sjis(raw_entry_content) # Strip trailing \0's entry_content = entry_content.rstrip('\0') else: entry_content = None if not has_content_section and (entry_size != parameter_size): raise Exception( 'Extra unannounced content in entry type: 0x%X, size: %s' % (entry_type, entry_size)) # Add entry self.entries.append( (entry_type, entry_parameters, entry_content))