static bool process_hier_data_pages_from_indexfile(FILE *f,
						   off_t seek_location,
						   int datafile_count,
						   char **datafile_names,
						   const char *dirname,
						   int zoom_levels,
						   struct _openslide_jpeg_layer **layers,
						   int tiles_across,
						   int tiles_down,
						   int image_divisions,
						   int32_t *tile_positions,
						   GList **jpegs_list,
						   struct _openslide_hash *quickhash1) {
  int32_t jpeg_number = 0;

  bool success = false;

  // used for storing which positions actually have data
  GHashTable *active_positions = g_hash_table_new_full(g_int_hash, g_int_equal,
						       g_free, NULL);

  for (int zoom_level = 0; zoom_level < zoom_levels; zoom_level++) {
    struct _openslide_jpeg_layer *l = layers[zoom_level];
    int32_t ptr;

    //    g_debug("reading zoom_level %d", zoom_level);

    if (fseeko(f, seek_location, SEEK_SET) == -1) {
      g_warning("Cannot seek to zoom level pointer %d", zoom_level + 1);
      goto DONE;
    }

    ptr = read_le_int32_from_file(f);
    if (ptr == -1) {
      g_warning("Can't read zoom level pointer");
      goto DONE;
    }
    if (fseeko(f, ptr, SEEK_SET) == -1) {
      g_warning("Cannot seek to start of data pages");
      goto DONE;
    }

    // read initial 0
    if (read_le_int32_from_file(f) != 0) {
      g_warning("Expected 0 value at beginning of data page");
      goto DONE;
    }

    // read pointer
    ptr = read_le_int32_from_file(f);
    if (ptr == -1) {
      g_warning("Can't read initial data page pointer");
      goto DONE;
    }

    // seek to offset
    if (fseeko(f, ptr, SEEK_SET) == -1) {
      g_warning("Can't seek to initial data page");
      goto DONE;
    }

    int32_t next_ptr;
    do {
      // read length
      int32_t page_len = read_le_int32_from_file(f);
      if (page_len == -1) {
	g_warning("Can't read page length");
	goto DONE;
      }

      //    g_debug("page_len: %d", page_len);

      // read "next" pointer
      next_ptr = read_le_int32_from_file(f);
      if (next_ptr == -1) {
	g_warning("Cannot read \"next\" pointer");
	goto DONE;
      }

      // read all the data into the list
      for (int i = 0; i < page_len; i++) {
	int32_t tile_index = read_le_int32_from_file(f);
	int32_t offset = read_le_int32_from_file(f);
	int32_t length = read_le_int32_from_file(f);
	int32_t fileno = read_le_int32_from_file(f);

	if (tile_index < 0) {
	  g_warning("tile_index < 0");
	  goto DONE;
	}
	if (offset < 0) {
	  g_warning("offset < 0");
	  goto DONE;
	}
	if (length < 0) {
	  g_warning("length < 0");
	  goto DONE;
	}
	if (fileno < 0) {
	  g_warning("fileno < 0");
	  goto DONE;
	}

	// we have only encountered images with exactly power-of-two scale
	// factors, and there appears to be no clear way to specify otherwise,
	// so require it
	int32_t x = tile_index % tiles_across;
	int32_t y = tile_index / tiles_across;

	if (y >= tiles_down) {
	  g_warning("y (%d) outside of bounds for zoom level (%d)",
		    y, zoom_level);
	  goto DONE;
	}

	if (x % (1 << zoom_level)) {
	  g_warning("x (%d) not correct multiple for zoom level (%d)",
		    x, zoom_level);
	  goto DONE;
	}
	if (y % (1 << zoom_level)) {
	  g_warning("y (%d) not correct multiple for zoom level (%d)",
		    y, zoom_level);
	  goto DONE;
	}

	// save filename
	if (fileno >= datafile_count) {
	  g_warning("Invalid fileno");
	  goto DONE;
	}
	char *filename = g_build_filename(dirname, datafile_names[fileno], NULL);

	// hash in the lowest-res on-disk tiles
	if (zoom_level == zoom_levels - 1) {
	  if (!_openslide_hash_file_part(quickhash1, filename, offset, length)) {
	    g_free(filename);
	    g_warning("Can't hash tiles");
	    goto DONE;
	  }
	}

	// populate the file structure
	struct _openslide_jpeg_file *jpeg = g_slice_new0(struct _openslide_jpeg_file);
	jpeg->filename = filename;
	jpeg->start_in_file = offset;
	jpeg->end_in_file = jpeg->start_in_file + length;
	jpeg->tw = l->raw_tile_width;
	jpeg->th = l->raw_tile_height;
	jpeg->w = l->raw_tile_width;
	jpeg->h = l->raw_tile_height;

	*jpegs_list = g_list_prepend(*jpegs_list, jpeg);


	// see comments elsewhere in this file
	const int tile_concat = 1 << zoom_level;
	const int tile_count_divisor = MIN(tile_concat, image_divisions);
	const int subtiles_per_jpeg_tile = MAX(1, tile_concat / image_divisions);
	const double subtile_w = (double) jpeg->w / subtiles_per_jpeg_tile;
	const double subtile_h = (double) jpeg->h / subtiles_per_jpeg_tile;

	const int tile0_w = layers[0]->raw_tile_width;
	const int tile0_h = layers[0]->raw_tile_height;

	// subtile_count: how many subtiles in a JPEG tile (one dimension)? this is constant
	//                for the first few levels, depending on image_divisions
	const int subtile_count = MAX(1, tile_concat / image_divisions);

	/*
	g_debug("tile_concat: %d, subtile_count: %d",
		tile_concat, subtile_count);
	g_debug("found %d %d from file", x, y);
	*/


	// start processing 1 JPEG tile into subtile_count^2 subtiles
	for (int yi = 0; yi < subtile_count; yi++) {
	  int yy = y + (yi * image_divisions);
	  if (yy >= tiles_down) {
	    break;
	  }

	  for (int xi = 0; xi < subtile_count; xi++) {
	    int xx = x + (xi * image_divisions);
	    if (xx >= tiles_across) {
	      break;
	    }

	    // xx and yy are the tile coordinates in level0 space

	    // look up the tile position, stored in 24.8 fixed point
	    int xp = xx / image_divisions;
	    int yp = yy / image_divisions;
	    int tp = yp * (tiles_across / image_divisions) + xp;
	    //g_debug("xx %d, yy %d, xp %d, yp %d, tp %d, spp %d, sc %d, tile0: %d %d subtile: %g %g", xx, yy, xp, yp, tp, subtiles_per_position, subtile_count, tile0_w, tile0_h, subtile_w, subtile_h);

	    if (zoom_level == 0) {
	      // if the zoom level is 0, then mark this position as active
	      int *key = g_new(int, 1);
	      *key = tp;
	      g_hash_table_insert(active_positions, key, NULL);
	    } else {
	      // make sure we have an active position for this tile
	      if (!g_hash_table_lookup_extended(active_positions, &tp, NULL, NULL)) {
		continue;
	      }
	    }

	    // position in layer 0
	    const double pos0_x = ((double) tile_positions[tp * 2]) / 256.0 +
	      tile0_w * (xx - xp * image_divisions);
	    const double pos0_y = ((double) tile_positions[(tp * 2) + 1]) / 256.0 +
	      tile0_h * (yy - yp * image_divisions);

	    // position in this layer
	    const double pos_x = pos0_x / tile_concat;
	    const double pos_y = pos0_y / tile_concat;

	    //g_debug("pos0: %g %g, pos: %g %g", pos0_x, pos0_y, pos_x, pos_y);

	    insert_subtile(l->tiles, jpeg_number,
			   pos_x, pos_y,
			   subtile_w * xi, subtile_h * yi,
			   subtile_w, subtile_h,
			   l->tile_advance_x, l->tile_advance_y,
			   x / tile_count_divisor + xi, y / tile_count_divisor + yi,
			   tiles_across / tile_count_divisor,
			   zoom_level);
	  }
	}
	jpeg_number++;
      }
    } while (next_ptr != 0);
static bool hamamatsu_vmu_part2(openslide_t *osr,
				int num_files, char **image_filenames,
				GError **err) {
  bool success = false;

  // initialize individual ngr structs
  struct _openslide_ngr **files = g_new0(struct _openslide_ngr *,
					 num_files);
  for (int i = 0; i < num_files; i++) {
    files[i] = g_slice_new0(struct _openslide_ngr);
  }

  // open files
  for (int i = 0; i < num_files; i++) {
    struct _openslide_ngr *ngr = files[i];

    ngr->filename = g_strdup(image_filenames[i]);

    FILE *f;
    if ((f = _openslide_fopen(ngr->filename, "rb", err)) == NULL) {
      goto DONE;
    }

    // validate magic
    if ((fgetc(f) != 'G') || (fgetc(f) != 'N')) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Bad magic on NGR file");
      fclose(f);
      goto DONE;
    }

    // read w, h, column width, headersize
    fseeko(f, 4, SEEK_SET);
    ngr->w = read_le_int32_from_file(f);
    ngr->h = read_le_int32_from_file(f);
    ngr->column_width = read_le_int32_from_file(f);

    fseeko(f, 24, SEEK_SET);
    ngr->start_in_file = read_le_int32_from_file(f);

    // validate
    if ((ngr->w <= 0) || (ngr->h <= 0) ||
	(ngr->column_width <= 0) || (ngr->start_in_file <= 0)) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Error processing header");
      fclose(f);
      goto DONE;
    }

    // ensure no remainder on columns
    if ((ngr->w % ngr->column_width) != 0) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Width not multiple of column width");
      fclose(f);
      goto DONE;
    }

    fclose(f);
  }

  success = true;

 DONE:
  if (success) {
    _openslide_add_ngr_ops(osr, num_files, files);
  } else {
    // destroy
    for (int i = 0; i < num_files; i++) {
      g_slice_free(struct _openslide_ngr, files[i]);
      g_free(files[i]->filename);
    }
    g_free(files);
  }

  return success;
}
static bool read_nonhier_record(FILE *f,
				int64_t nonhier_root_position,
				int recordno,
				int *fileno, int64_t *size, int64_t *position) {
  if (recordno == -1)
    return false;

  if (fseeko(f, nonhier_root_position, SEEK_SET) == -1) {
    g_warning("Cannot seek to nonhier root");
    return false;
  }

  int32_t ptr = read_le_int32_from_file(f);
  if (ptr == -1) {
    g_warning("Can't read initial nonhier pointer");
    return false;
  }

  // jump to start of interesting data
  //  g_debug("seek %d", ptr);
  if (fseeko(f, ptr, SEEK_SET) == -1) {
    g_warning("Cannot seek to start of nonhier data");
    return false;
  }

  // seek to record pointer
  if (fseeko(f, recordno * 4, SEEK_CUR) == -1) {
    g_warning("Cannot seek to nonhier record pointer %d", recordno);
    return false;
  }

  // read pointer
  ptr = read_le_int32_from_file(f);
  if (ptr == -1) {
    g_warning("Can't read nonhier record %d", recordno);
    return false;
  }

  // seek
  if (fseeko(f, ptr, SEEK_SET) == -1) {
    g_warning("Cannot seek to nonhier record %d", recordno);
    return false;
  }

  // read initial 0
  if (read_le_int32_from_file(f) != 0) {
    g_warning("Expected 0 value at beginning of data page");
    return false;
  }

  // read pointer
  ptr = read_le_int32_from_file(f);
  if (ptr == -1) {
    g_warning("Can't read initial data page pointer");
    return false;
  }

  // seek to offset
  if (fseeko(f, ptr, SEEK_SET) == -1) {
    g_warning("Can't seek to initial data page");
    return false;
  }

  // read pagesize == 1
  if (read_le_int32_from_file(f) != 1) {
    g_warning("Expected 1 value");
    return false;
  }

  // read 3 zeroes
  if (read_le_int32_from_file(f) != 0) {
    g_warning("Expected first 0 value");
    return false;
  }
  if (read_le_int32_from_file(f) != 0) {
    g_warning("Expected second 0 value");
    return false;
  }
  if (read_le_int32_from_file(f) != 0) {
    g_warning("Expected third 0 value");
    return false;
  }

  // finally read offset, size, fileno
  *position = read_le_int32_from_file(f);
  if (*position == -1) {
    g_warning("Can't read position");
    return false;
  }
  *size = read_le_int32_from_file(f);
  if (*size == -1) {
    g_warning("Can't read size");
    return false;
  }
  *fileno = read_le_int32_from_file(f);
  if (*fileno == -1) {
    g_warning("Can't read fileno");
    return false;
  }

  return true;
}