def read_file filename
      
      # The first block location is stored in the hash table.
      hash_a = Hashing::hash_for :hash_a, filename
      hash_b = Hashing::hash_for :hash_b, filename
      hash_entry = @hash_table.find do |h|
        [h.hash_a, h.hash_b] == [hash_a, hash_b]
      end
      unless hash_entry
        return nil
      end
      block_entry = @block_table[hash_entry.block_index]
      unless block_entry.file?
        return nil 
      end
      @io.seek @user_header.archive_header_offset + block_entry.block_offset
      file_data = @io.read block_entry.archived_size
      
      # Blocks can be encrypted. Decryption isn't currently implemented as none 
      # of the blocks in a StarCraft 2 replay are encrypted.
      if block_entry.encrypted?
        return nil
      end
      
      # Files can consist of one or many blocks. In either case, each block 
      # (or *sector*) is read and individually decompressed if needed, then 
      # stitched together for the final result.
      if block_entry.single_unit?
        if block_entry.compressed?
          if file_data.bytes.next == 16
            file_data = Bzip2.uncompress file_data[1, file_data.length]
          end
        end
        return file_data 
      end
      sector_size = 512 << @archive_header.sector_size_shift
      sectors = block_entry.size / sector_size + 1
      if block_entry.has_checksums
        sectors += 1
      end
      positions = file_data[0, 4 * (sectors + 1)].unpack "V#{sectors + 1}"
      sectors = []
      positions.each_with_index do |pos, i|
        break if i + 1 == positions.length
        sector = file_data[pos, positions[i + 1] - pos]
        if block_entry.compressed?
          if block_entry.size > block_entry.archived_size
            if sector.bytes.next == 16
              sector = Bzip2.uncompress sector
            end
          end
        end
        sectors << sector
      end
      sectors.join ''
    end
def read_file filename
      
      # The first block location is stored in the hash table.
      hash_a = Hashing::hash_for :hash_a, filename
      hash_b = Hashing::hash_for :hash_b, filename
      hash_entry = @hash_table.find do |h|
        [h.hash_a, h.hash_b] == [hash_a, hash_b]
      end
      unless hash_entry
        return nil
      end
      block_entry = @block_table[hash_entry.block_index]
      unless block_entry.file?
        return nil 
      end
      @io.seek @user_header.archive_header_offset + block_entry.block_offset
      file_data = @io.read block_entry.archived_size
      
      # Blocks can be encrypted. Decryption isn't currently implemented as none 
      # of the blocks in a StarCraft 2 replay are encrypted.
      if block_entry.encrypted?
        return nil
      end
      
      # Files can consist of one or many blocks. In either case, each block 
      # (or *sector*) is read and individually decompressed if needed, then 
      # stitched together for the final result.
      if block_entry.single_unit?
        if block_entry.compressed?
          if file_data.bytes.next == 16
            file_data = Bzip2.uncompress file_data[1, file_data.length]
          end
        end
        return file_data 
      end
      sector_size = 512 << @archive_header.sector_size_shift
      sectors = block_entry.size / sector_size + 1
      if block_entry.has_checksums
        sectors += 1
      end
      positions = file_data[0, 4 * (sectors + 1)].unpack "V#{sectors + 1}"
      sectors = []
      positions.each_with_index do |pos, i|
        break if i + 1 == positions.length
        sector = file_data[pos, positions[i + 1] - pos]
        if block_entry.compressed?
          if block_entry.size > block_entry.archived_size
            if sector.bytes.next == 16
              sector = Bzip2.uncompress sector
            end
          end
        end
        sectors << sector
      end
      sectors.join ''
    end
To read a file from the MPQ archive, we need to locate its blocks.