diff --git a/src/Makefile.am b/src/Makefile.am
index 5130264a529d4425d8c49ec964820c604bdc01e3..adabaa7095470b9fec781d2ae586760c80b1c6c6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -111,8 +111,9 @@ AM_SOURCES = space.c space_unique_id.c \
     engine_marktasks.c engine_drift.c engine_unskip.c engine_collect_end_of_step.c \
     engine_redistribute.c engine_fof.c \
     queue.c task.c timers.c debug.c scheduler.c proxy.c version.c \
-    parallel_io.c common_io.c single_io.c serial_io.c distributed_io.c xmf.c \
-    output_options.c line_of_sight.c restart.c parser.c \
+    common_io.c common_io_copy.c common_io_cells.c common_io_fields.c \
+    single_io.c serial_io.c distributed_io.c parallel_io.c \
+    output_options.c line_of_sight.c restart.c parser.c xmf.c \
     kernel_hydro.c tools.c map.c part.c partition.c clocks.c  \
     physical_constants.c units.c potential.c hydro_properties.c \
     threadpool.c cooling.c star_formation.c \
diff --git a/src/common_io.c b/src/common_io.c
index ec0d085d914dffa8093b4a67b0b08647097159c5..081d6edac332f934ef0d914c0796783661cd2b36 100644
--- a/src/common_io.c
+++ b/src/common_io.c
@@ -24,35 +24,24 @@
 /* This object's header. */
 #include "common_io.h"
 
-/* Pre-inclusion as needed in other headers */
-#include "engine.h"
-
 /* Local includes. */
-#include "black_holes_io.h"
-#include "chemistry_io.h"
-#include "const.h"
-#include "cooling_io.h"
+#include "engine.h"
 #include "error.h"
-#include "feedback.h"
-#include "fof_io.h"
-#include "gravity_io.h"
-#include "hydro.h"
-#include "hydro_io.h"
-#include "io_properties.h"
 #include "kernel_hydro.h"
 #include "part.h"
 #include "part_type.h"
-#include "rt_io.h"
-#include "sink_io.h"
-#include "star_formation_io.h"
-#include "stars_io.h"
 #include "threadpool.h"
 #include "tools.h"
-#include "tracers_io.h"
-#include "units.h"
-#include "velociraptor_io.h"
 #include "version.h"
 
+/* I/O functions of each sub-module */
+#include "chemistry_io.h"
+#include "cooling_io.h"
+#include "feedback.h"
+#include "hydro_io.h"
+#include "stars_io.h"
+#include "tracers_io.h"
+
 /* Some standard headers. */
 #include <math.h>
 #include <stddef.h>
@@ -763,457 +752,6 @@ void io_write_engine_policy(hid_t h_file, const struct engine* e) {
   H5Gclose(h_grp);
 }
 
-static long long cell_count_non_inhibited_gas(const struct cell* c) {
-  const int total_count = c->hydro.count;
-  struct part* parts = c->hydro.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((parts[i].time_bin != time_bin_inhibited) &&
-        (parts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_dark_matter(const struct cell* c) {
-  const int total_count = c->grav.count;
-  struct gpart* gparts = c->grav.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((gparts[i].time_bin != time_bin_inhibited) &&
-        (gparts[i].time_bin != time_bin_not_created) &&
-        (gparts[i].type == swift_type_dark_matter)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_background_dark_matter(
-    const struct cell* c) {
-  const int total_count = c->grav.count;
-  struct gpart* gparts = c->grav.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((gparts[i].time_bin != time_bin_inhibited) &&
-        (gparts[i].time_bin != time_bin_not_created) &&
-        (gparts[i].type == swift_type_dark_matter_background)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_stars(const struct cell* c) {
-  const int total_count = c->stars.count;
-  struct spart* sparts = c->stars.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((sparts[i].time_bin != time_bin_inhibited) &&
-        (sparts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_black_holes(const struct cell* c) {
-  const int total_count = c->black_holes.count;
-  struct bpart* bparts = c->black_holes.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((bparts[i].time_bin != time_bin_inhibited) &&
-        (bparts[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-static long long cell_count_non_inhibited_sinks(const struct cell* c) {
-  const int total_count = c->sinks.count;
-  struct sink* sinks = c->sinks.parts;
-  long long count = 0;
-  for (int i = 0; i < total_count; ++i) {
-    if ((sinks[i].time_bin != time_bin_inhibited) &&
-        (sinks[i].time_bin != time_bin_not_created)) {
-      ++count;
-    }
-  }
-  return count;
-}
-
-/**
- * @brief Write a single 1D array to a hdf5 group.
- *
- * This creates a simple Nx1 array with a chunk size of 1024x1.
- * The Fletcher-32 filter is applied to the array.
- *
- * @param h_grp The open hdf5 group.
- * @param n The number of elements in the array.
- * @param array The data to write.
- * @param type The type of the data to write.
- * @param name The name of the array.
- * @param array_content The name of the parent group (only used for error
- * messages).
- */
-void io_write_array(hid_t h_grp, const int n, const void* array,
-                    const enum IO_DATA_TYPE type, const char* name,
-                    const char* array_content) {
-
-  /* Create memory space */
-  const hsize_t shape[2] = {(hsize_t)n, 1};
-  hid_t h_space = H5Screate(H5S_SIMPLE);
-  if (h_space < 0)
-    error("Error while creating data space for %s %s", name, array_content);
-  hid_t h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
-  if (h_err < 0)
-    error("Error while changing shape of %s %s data space.", name,
-          array_content);
-
-  /* Dataset type */
-  hid_t h_type = H5Tcopy(io_hdf5_type(type));
-
-  const hsize_t chunk[2] = {(1024 > n ? n : 1024), 1};
-  hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
-  h_err = H5Pset_chunk(h_prop, 1, chunk);
-  if (h_err < 0)
-    error("Error while setting chunk shapes of %s %s data space.", name,
-          array_content);
-
-  /* Impose check-sum to verify data corruption */
-  h_err = H5Pset_fletcher32(h_prop);
-  if (h_err < 0)
-    error("Error while setting check-sum filter on %s %s data space.", name,
-          array_content);
-
-  /* Write */
-  hid_t h_data =
-      H5Dcreate(h_grp, name, h_type, h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
-  if (h_data < 0)
-    error("Error while creating dataspace for %s %s.", name, array_content);
-  h_err = H5Dwrite(h_data, io_hdf5_type(type), h_space, H5S_ALL, H5P_DEFAULT,
-                   array);
-  if (h_err < 0) error("Error while writing %s %s.", name, array_content);
-  H5Tclose(h_type);
-  H5Dclose(h_data);
-  H5Pclose(h_prop);
-  H5Sclose(h_space);
-}
-
-void io_write_cell_offsets(hid_t h_grp, const int cdim[3], const double dim[3],
-                           const struct cell* cells_top, const int nr_cells,
-                           const double width[3], const int nodeID,
-                           const int distributed,
-                           const long long global_counts[swift_type_count],
-                           const long long global_offsets[swift_type_count],
-                           const int num_fields[swift_type_count],
-                           const struct unit_system* internal_units,
-                           const struct unit_system* snapshot_units) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-  if (distributed) {
-    if (global_offsets[0] != 0 || global_offsets[1] != 0 ||
-        global_offsets[2] != 0 || global_offsets[3] != 0 ||
-        global_offsets[4] != 0 || global_offsets[5] != 0)
-      error("Global offset non-zero in the distributed io case!");
-  }
-#endif
-
-  /* Abort if we don't have any cells yet (i.e. haven't constructed the space)
-   */
-  if (nr_cells == 0) return;
-
-  double cell_width[3] = {width[0], width[1], width[2]};
-
-  /* Temporary memory for the cell-by-cell information */
-  double* centres = NULL;
-  centres = (double*)malloc(3 * nr_cells * sizeof(double));
-
-  /* Temporary memory for the cell files ID */
-  int* files = NULL;
-  files = (int*)malloc(nr_cells * sizeof(int));
-
-  /* Count of particles in each cell */
-  long long *count_part = NULL, *count_gpart = NULL,
-            *count_background_gpart = NULL, *count_spart = NULL,
-            *count_bpart = NULL, *count_sink = NULL;
-  count_part = (long long*)malloc(nr_cells * sizeof(long long));
-  count_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_spart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_bpart = (long long*)malloc(nr_cells * sizeof(long long));
-  count_sink = (long long*)malloc(nr_cells * sizeof(long long));
-
-  /* Global offsets of particles in each cell */
-  long long *offset_part = NULL, *offset_gpart = NULL,
-            *offset_background_gpart = NULL, *offset_spart = NULL,
-            *offset_bpart = NULL, *offset_sink = NULL;
-  offset_part = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_spart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_bpart = (long long*)malloc(nr_cells * sizeof(long long));
-  offset_sink = (long long*)malloc(nr_cells * sizeof(long long));
-
-  /* Offsets of the 0^th element */
-  offset_part[0] = 0;
-  offset_gpart[0] = 0;
-  offset_background_gpart[0] = 0;
-  offset_spart[0] = 0;
-  offset_bpart[0] = 0;
-  offset_sink[0] = 0;
-
-  /* Collect the cell information of *local* cells */
-  long long local_offset_part = 0;
-  long long local_offset_gpart = 0;
-  long long local_offset_background_gpart = 0;
-  long long local_offset_spart = 0;
-  long long local_offset_bpart = 0;
-  long long local_offset_sink = 0;
-  for (int i = 0; i < nr_cells; ++i) {
-
-    /* Store in which file this cell will be found */
-    if (distributed) {
-      files[i] = cells_top[i].nodeID;
-    } else {
-      files[i] = 0;
-    }
-
-    /* Is the cell on this node (i.e. we have full information */
-    if (cells_top[i].nodeID == nodeID) {
-
-      /* Centre of each cell */
-      centres[i * 3 + 0] = cells_top[i].loc[0] + cell_width[0] * 0.5;
-      centres[i * 3 + 1] = cells_top[i].loc[1] + cell_width[1] * 0.5;
-      centres[i * 3 + 2] = cells_top[i].loc[2] + cell_width[2] * 0.5;
-
-      /* Finish by box wrapping to match what is done to the particles */
-      centres[i * 3 + 0] = box_wrap(centres[i * 3 + 0], 0.0, dim[0]);
-      centres[i * 3 + 1] = box_wrap(centres[i * 3 + 1], 0.0, dim[1]);
-      centres[i * 3 + 2] = box_wrap(centres[i * 3 + 2], 0.0, dim[2]);
-
-      /* Count real particles that will be written */
-      count_part[i] = cell_count_non_inhibited_gas(&cells_top[i]);
-      count_gpart[i] = cell_count_non_inhibited_dark_matter(&cells_top[i]);
-      count_background_gpart[i] =
-          cell_count_non_inhibited_background_dark_matter(&cells_top[i]);
-      count_spart[i] = cell_count_non_inhibited_stars(&cells_top[i]);
-      count_bpart[i] = cell_count_non_inhibited_black_holes(&cells_top[i]);
-      count_sink[i] = cell_count_non_inhibited_sinks(&cells_top[i]);
-
-      /* Offsets including the global offset of all particles on this MPI rank
-       * Note that in the distributed case, the global offsets are 0 such that
-       * we actually compute the offset in the file written by this rank. */
-      offset_part[i] = local_offset_part + global_offsets[swift_type_gas];
-      offset_gpart[i] =
-          local_offset_gpart + global_offsets[swift_type_dark_matter];
-      offset_background_gpart[i] =
-          local_offset_background_gpart +
-          global_offsets[swift_type_dark_matter_background];
-      offset_spart[i] = local_offset_spart + global_offsets[swift_type_stars];
-      offset_bpart[i] =
-          local_offset_bpart + global_offsets[swift_type_black_hole];
-      offset_sink[i] = local_offset_sink + global_offsets[swift_type_sink];
-
-      local_offset_part += count_part[i];
-      local_offset_gpart += count_gpart[i];
-      local_offset_background_gpart += count_background_gpart[i];
-      local_offset_spart += count_spart[i];
-      local_offset_bpart += count_bpart[i];
-      local_offset_sink += count_sink[i];
-
-    } else {
-
-      /* Just zero everything for the foregin cells */
-
-      centres[i * 3 + 0] = 0.;
-      centres[i * 3 + 1] = 0.;
-      centres[i * 3 + 2] = 0.;
-
-      count_part[i] = 0;
-      count_gpart[i] = 0;
-      count_background_gpart[i] = 0;
-      count_spart[i] = 0;
-      count_bpart[i] = 0;
-      count_sink[i] = 0;
-
-      offset_part[i] = 0;
-      offset_gpart[i] = 0;
-      offset_background_gpart[i] = 0;
-      offset_spart[i] = 0;
-      offset_bpart[i] = 0;
-      offset_sink[i] = 0;
-    }
-  }
-
-#ifdef WITH_MPI
-  /* Now, reduce all the arrays. Note that we use a bit-wise OR here. This
-     is safe as we made sure only local cells have non-zero values. */
-  MPI_Allreduce(MPI_IN_PLACE, count_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_gpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_background_gpart, nr_cells,
-                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_spart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, count_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-
-  MPI_Allreduce(MPI_IN_PLACE, offset_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_gpart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_background_gpart, nr_cells,
-                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
-                MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_spart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-  MPI_Allreduce(MPI_IN_PLACE, offset_bpart, nr_cells, MPI_LONG_LONG_INT,
-                MPI_BOR, MPI_COMM_WORLD);
-
-  /* For the centres we use a sum as MPI does not like bit-wise operations
-     on floating point numbers */
-  MPI_Allreduce(MPI_IN_PLACE, centres, 3 * nr_cells, MPI_DOUBLE, MPI_SUM,
-                MPI_COMM_WORLD);
-#endif
-
-  /* When writing a single file, only rank 0 writes the meta-data */
-  if ((distributed) || (!distributed && nodeID == 0)) {
-
-    /* Unit conversion if necessary */
-    const double factor = units_conversion_factor(
-        internal_units, snapshot_units, UNIT_CONV_LENGTH);
-    if (factor != 1.) {
-
-      /* Convert the cell centres */
-      for (int i = 0; i < nr_cells; ++i) {
-        centres[i * 3 + 0] *= factor;
-        centres[i * 3 + 1] *= factor;
-        centres[i * 3 + 2] *= factor;
-      }
-
-      /* Convert the cell widths */
-      cell_width[0] *= factor;
-      cell_width[1] *= factor;
-      cell_width[2] *= factor;
-    }
-
-    /* Write some meta-information first */
-    hid_t h_subgrp =
-        H5Gcreate(h_grp, "Meta-data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_subgrp < 0) error("Error while creating meta-data sub-group");
-    io_write_attribute(h_subgrp, "nr_cells", INT, &nr_cells, 1);
-    io_write_attribute(h_subgrp, "size", DOUBLE, cell_width, 3);
-    io_write_attribute(h_subgrp, "dimension", INT, cdim, 3);
-    H5Gclose(h_subgrp);
-
-    /* Write the centres to the group */
-    hsize_t shape[2] = {(hsize_t)nr_cells, 3};
-    hid_t h_space = H5Screate(H5S_SIMPLE);
-    if (h_space < 0) error("Error while creating data space for cell centres");
-    hid_t h_err = H5Sset_extent_simple(h_space, 2, shape, shape);
-    if (h_err < 0)
-      error("Error while changing shape of gas offsets data space.");
-    hid_t h_data = H5Dcreate(h_grp, "Centres", io_hdf5_type(DOUBLE), h_space,
-                             H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_data < 0) error("Error while creating dataspace for gas offsets.");
-    h_err = H5Dwrite(h_data, io_hdf5_type(DOUBLE), h_space, H5S_ALL,
-                     H5P_DEFAULT, centres);
-    if (h_err < 0) error("Error while writing centres.");
-    H5Dclose(h_data);
-    H5Sclose(h_space);
-
-    /* Group containing the offsets and counts for each particle type */
-    hid_t h_grp_offsets = H5Gcreate(h_grp, "OffsetsInFile", H5P_DEFAULT,
-                                    H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_offsets < 0) error("Error while creating offsets sub-group");
-    hid_t h_grp_files =
-        H5Gcreate(h_grp, "Files", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_files < 0) error("Error while creating filess sub-group");
-    hid_t h_grp_counts =
-        H5Gcreate(h_grp, "Counts", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
-    if (h_grp_counts < 0) error("Error while creating counts sub-group");
-
-    if (global_counts[swift_type_gas] > 0 && num_fields[swift_type_gas] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType0", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_part, LONGLONG,
-                     "PartType0", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_part, LONGLONG, "PartType0",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_dark_matter] > 0 &&
-        num_fields[swift_type_dark_matter] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType1", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_gpart, LONGLONG,
-                     "PartType1", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_gpart, LONGLONG, "PartType1",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_dark_matter_background] > 0 &&
-        num_fields[swift_type_dark_matter_background] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType2", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_background_gpart, LONGLONG,
-                     "PartType2", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_background_gpart, LONGLONG,
-                     "PartType2", "counts");
-    }
-
-    if (global_counts[swift_type_sink] > 0 && num_fields[swift_type_sink] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType3", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_sink, LONGLONG,
-                     "PartType3", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_sink, LONGLONG, "PartType3",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_stars] > 0 &&
-        num_fields[swift_type_stars] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType4", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_spart, LONGLONG,
-                     "PartType4", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_spart, LONGLONG, "PartType4",
-                     "counts");
-    }
-
-    if (global_counts[swift_type_black_hole] > 0 &&
-        num_fields[swift_type_black_hole] > 0) {
-      io_write_array(h_grp_files, nr_cells, files, INT, "PartType5", "files");
-      io_write_array(h_grp_offsets, nr_cells, offset_bpart, LONGLONG,
-                     "PartType5", "offsets");
-      io_write_array(h_grp_counts, nr_cells, count_bpart, LONGLONG, "PartType5",
-                     "counts");
-    }
-
-    H5Gclose(h_grp_offsets);
-    H5Gclose(h_grp_files);
-    H5Gclose(h_grp_counts);
-  }
-
-  /* Free everything we allocated */
-  free(centres);
-  free(files);
-  free(count_part);
-  free(count_gpart);
-  free(count_background_gpart);
-  free(count_spart);
-  free(count_bpart);
-  free(count_sink);
-  free(offset_part);
-  free(offset_gpart);
-  free(offset_background_gpart);
-  free(offset_spart);
-  free(offset_bpart);
-  free(offset_sink);
-}
-
 #endif /* HAVE_HDF5 */
 
 /**
@@ -1250,730 +788,6 @@ size_t io_sizeof_type(enum IO_DATA_TYPE type) {
   }
 }
 
-/**
- * @brief Mapper function to copy #part or #gpart fields into a buffer.
- */
-void io_copy_mapper(void* restrict temp, int N, void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const size_t typeSize = io_sizeof_type(props.type);
-  const size_t copySize = typeSize * props.dimension;
-
-  /* How far are we with this chunk? */
-  char* restrict temp_c = (char*)temp;
-  const ptrdiff_t delta = (temp_c - props.start_temp_c) / copySize;
-
-  for (int k = 0; k < N; k++) {
-    memcpy(&temp_c[k * copySize], props.field + (delta + k) * props.partSize,
-           copySize);
-  }
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of floats using a
- * conversion function.
- */
-void io_convert_part_f_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_f(e, parts + delta + i, xparts + delta + i,
-                         &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of ints using a
- * conversion function.
- */
-void io_convert_part_i_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_i(e, parts + delta + i, xparts + delta + i,
-                         &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_part_d_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_d(e, parts + delta + i, xparts + delta + i,
-                         &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #part into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_part_l_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct part* restrict parts = props.parts;
-  const struct xpart* restrict xparts = props.xparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_part_l(e, parts + delta + i, xparts + delta + i,
-                         &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_gpart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_f(e, gparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_gpart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_i(e, gparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_gpart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_d(e, gparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #gpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_gpart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct gpart* restrict gparts = props.gparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_gpart_l(e, gparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_spart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_f(e, sparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_spart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_i(e, sparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_spart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_d(e, sparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #spart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_spart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct spart* restrict sparts = props.sparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_spart_l(e, sparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of floats using a
- * conversion function.
- */
-void io_convert_bpart_f_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_f(e, bparts + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of ints using a
- * conversion function.
- */
-void io_convert_bpart_i_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_i(e, bparts + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_bpart_d_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_d(e, bparts + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #bpart into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_bpart_l_mapper(void* restrict temp, int N,
-                               void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct bpart* restrict bparts = props.bparts;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_bpart_l(e, bparts + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of floats using a
- * conversion function.
- */
-void io_convert_sink_f_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  float* restrict temp_f = (float*)temp;
-  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_f(e, sinks + delta + i, &temp_f[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of ints using a
- * conversion function.
- */
-void io_convert_sink_i_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  int* restrict temp_i = (int*)temp;
-  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_i(e, sinks + delta + i, &temp_i[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_sink_d_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  double* restrict temp_d = (double*)temp;
-  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_d(e, sinks + delta + i, &temp_d[i * dim]);
-}
-
-/**
- * @brief Mapper function to copy #sink into a buffer of doubles using a
- * conversion function.
- */
-void io_convert_sink_l_mapper(void* restrict temp, int N,
-                              void* restrict extra_data) {
-
-  const struct io_props props = *((const struct io_props*)extra_data);
-  const struct sink* restrict sinks = props.sinks;
-  const struct engine* e = props.e;
-  const size_t dim = props.dimension;
-
-  /* How far are we with this chunk? */
-  long long* restrict temp_l = (long long*)temp;
-  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
-
-  for (int i = 0; i < N; i++)
-    props.convert_sink_l(e, sinks + delta + i, &temp_l[i * dim]);
-}
-
-/**
- * @brief Copy the particle data into a temporary buffer ready for i/o.
- *
- * @param temp The buffer to be filled. Must be allocated and aligned properly.
- * @param e The #engine.
- * @param props The #io_props corresponding to the particle field we are
- * copying.
- * @param N The number of particles to copy
- * @param internal_units The system of units used internally.
- * @param snapshot_units The system of units used for the snapshots.
- */
-void io_copy_temp_buffer(void* temp, const struct engine* e,
-                         struct io_props props, size_t N,
-                         const struct unit_system* internal_units,
-                         const struct unit_system* snapshot_units) {
-
-  const size_t typeSize = io_sizeof_type(props.type);
-  const size_t copySize = typeSize * props.dimension;
-  const size_t num_elements = N * props.dimension;
-
-  /* Copy particle data to temporary buffer */
-  if (props.conversion == 0) { /* No conversion */
-
-    /* Prepare some parameters */
-    char* temp_c = (char*)temp;
-    props.start_temp_c = temp_c;
-
-    /* Copy the whole thing into a buffer */
-    threadpool_map((struct threadpool*)&e->threadpool, io_copy_mapper, temp_c,
-                   N, copySize, threadpool_auto_chunk_size, (void*)&props);
-
-  } else { /* Converting particle to data */
-
-    if (props.convert_part_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_part_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_part_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_gpart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_gpart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_spart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_spart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_sink_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_sink_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_f != NULL) {
-
-      /* Prepare some parameters */
-      float* temp_f = (float*)temp;
-      props.start_temp_f = (float*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_f_mapper, temp_f, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_i != NULL) {
-
-      /* Prepare some parameters */
-      int* temp_i = (int*)temp;
-      props.start_temp_i = (int*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_i_mapper, temp_i, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_d != NULL) {
-
-      /* Prepare some parameters */
-      double* temp_d = (double*)temp;
-      props.start_temp_d = (double*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_d_mapper, temp_d, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else if (props.convert_bpart_l != NULL) {
-
-      /* Prepare some parameters */
-      long long* temp_l = (long long*)temp;
-      props.start_temp_l = (long long*)temp;
-      props.e = e;
-
-      /* Copy the whole thing into a buffer */
-      threadpool_map((struct threadpool*)&e->threadpool,
-                     io_convert_bpart_l_mapper, temp_l, N, copySize,
-                     threadpool_auto_chunk_size, (void*)&props);
-
-    } else {
-      error("Missing conversion function");
-    }
-  }
-
-  /* Unit conversion if necessary */
-  const double factor =
-      units_conversion_factor(internal_units, snapshot_units, props.units);
-  if (factor != 1.) {
-
-    /* message("Converting ! factor=%e", factor); */
-
-    if (io_is_double_precision(props.type)) {
-      swift_declare_aligned_ptr(double, temp_d, (double*)temp,
-                                IO_BUFFER_ALIGNMENT);
-      for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
-    } else {
-      swift_declare_aligned_ptr(float, temp_f, (float*)temp,
-                                IO_BUFFER_ALIGNMENT);
-      for (size_t i = 0; i < num_elements; ++i) temp_f[i] *= factor;
-    }
-  }
-}
-
 void io_prepare_dm_gparts_mapper(void* restrict data, int Ndm, void* dummy) {
 
   struct gpart* restrict gparts = (struct gpart*)data;
@@ -2536,243 +1350,6 @@ void io_collect_gparts_background_to_write(
           count, Ngparts_written);
 }
 
-/**
- * @brief Prepare the output option fields according to the user's choices and
- * verify that they are valid.
- *
- * @param output_options The #output_options for this run
- * @param with_cosmology Ran with cosmology?
- * @param with_fof Are we running with on-the-fly Fof?
- * @param with_stf Are we running with on-the-fly structure finder?
- */
-void io_prepare_output_fields(struct output_options* output_options,
-                              const int with_cosmology, const int with_fof,
-                              const int with_stf) {
-
-  const int MAX_NUM_PTYPE_FIELDS = 100;
-
-  /* Parameter struct for the output options */
-  struct swift_params* params = output_options->select_output;
-
-  /* Get all possible outputs per particle type */
-  int ptype_num_fields_total[swift_type_count] = {0};
-  struct io_props field_list[swift_type_count][MAX_NUM_PTYPE_FIELDS];
-
-  for (int ptype = 0; ptype < swift_type_count; ptype++)
-    ptype_num_fields_total[ptype] = get_ptype_fields(
-        ptype, field_list[ptype], with_cosmology, with_fof, with_stf);
-
-  /* Check for whether we have a `Default` section */
-  int have_default = 0;
-
-  /* Loop over each section, i.e. different class of output */
-  for (int section_id = 0; section_id < params->sectionCount; section_id++) {
-
-    /* Get the name of current (selection) section, without a trailing colon */
-    char section_name[FIELD_BUFFER_SIZE];
-    strcpy(section_name, params->section[section_id].name);
-    section_name[strlen(section_name) - 1] = 0;
-
-    /* Is this the `Default` section? */
-    if (strcmp(section_name, select_output_header_default_name) == 0)
-      have_default = 1;
-
-    /* How many fields should each ptype write by default? */
-    int ptype_num_fields_to_write[swift_type_count];
-
-    /* What is the default writing status for each ptype (on/off)? */
-    int ptype_default_write_status[swift_type_count];
-
-    /* Initialise section-specific writing counters for each particle type.
-     * If default is 'write', then we start from the total to deduct any fields
-     * that are switched off. If the default is 'off', we have to start from
-     * zero and then count upwards for each field that is switched back on. */
-    for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-      /* Internally also verifies that the default level is allowed */
-      const enum lossy_compression_schemes compression_level_current_default =
-          output_options_get_ptype_default_compression(params, section_name,
-                                                       (enum part_type)ptype);
-
-      if (compression_level_current_default == compression_do_not_write) {
-        ptype_default_write_status[ptype] = 0;
-        ptype_num_fields_to_write[ptype] = 0;
-      } else {
-        ptype_default_write_status[ptype] = 1;
-        ptype_num_fields_to_write[ptype] = ptype_num_fields_total[ptype];
-      }
-
-    } /* ends loop over particle types */
-
-    /* Loop over each parameter */
-    for (int param_id = 0; param_id < params->paramCount; param_id++) {
-
-      /* Full name of the parameter to check */
-      const char* param_name = params->data[param_id].name;
-
-      /* Check whether the file still contains the old, now inappropriate
-       * 'SelectOutput' section */
-      if (strstr(param_name, "SelectOutput:") != NULL) {
-        error(
-            "Output selection files no longer require the use of top level "
-            "SelectOutput; see the documentation for changes.");
-      }
-
-      /* Skip if the parameter belongs to another output class or is a
-       * 'Standard' parameter */
-      if (strstr(param_name, section_name) == NULL) continue;
-      if (strstr(param_name, ":Standard_") != NULL) continue;
-
-      /* Get the particle type for current parameter
-       * (raises an error if it could not determine it) */
-      const int param_ptype = get_param_ptype(param_name);
-
-      /* Issue a warning if this parameter does not pertain to any of the
-       * known fields from this ptype. */
-      int field_id = 0;
-      char field_name[PARSER_MAX_LINE_SIZE];
-      for (field_id = 0; field_id < ptype_num_fields_total[param_ptype];
-           field_id++) {
-
-        sprintf(field_name, "%s:%.*s_%s", section_name, FIELD_BUFFER_SIZE,
-                field_list[param_ptype][field_id].name,
-                part_type_names[param_ptype]);
-
-        if (strcmp(param_name, field_name) == 0) break;
-      }
-
-      int param_is_known = 0; /* Update below if it is a known one */
-      if (field_id < ptype_num_fields_total[param_ptype])
-        param_is_known = 1;
-      else
-        message(
-            "WARNING: Trying to change behaviour of field '%s' (read from "
-            "'%s') that does not exist. This may be because you are not "
-            "running with all of the physics that you compiled the code with.",
-            param_name, params->fileName);
-
-      /* Perform a correctness check on the _value_ of the parameter */
-      char param_value[FIELD_BUFFER_SIZE];
-      parser_get_param_string(params, param_name, param_value);
-
-      int value_id = 0;
-      for (value_id = 0; value_id < compression_level_count; value_id++)
-        if (strcmp(param_value, lossy_compression_schemes_names[value_id]) == 0)
-          break;
-
-      if (value_id == compression_level_count)
-        error("Choice of output selection parameter %s ('%s') is invalid.",
-              param_name, param_value);
-
-      /* Adjust number of fields to be written for param_ptype, if this field's
-       * status is different from default and it is a known one. */
-      if (param_is_known) {
-        const int is_on =
-            strcmp(param_value,
-                   lossy_compression_schemes_names[compression_do_not_write]) !=
-            0;
-
-        if (is_on && !ptype_default_write_status[param_ptype]) {
-          /* Particle should be written even though default is off:
-           * increase field count */
-          ptype_num_fields_to_write[param_ptype] += 1;
-        }
-        if (!is_on && ptype_default_write_status[param_ptype]) {
-          /* Particle should not be written, even though default is on:
-           * decrease field count */
-          ptype_num_fields_to_write[param_ptype] -= 1;
-        }
-      }
-    } /* ends loop over parameters */
-
-    /* Second loop over ptypes, to write out total number of fields to write */
-    for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-#ifdef SWIFT_DEBUG_CHECKS
-      /* Sanity check: is the number of fields to write non-negative? */
-      if (ptype_num_fields_to_write[ptype] < 0)
-        error(
-            "We seem to have subtracted too many fields for particle "
-            "type %d in output class %s (total to write is %d)",
-            ptype, section_name, ptype_num_fields_to_write[ptype]);
-#endif
-      output_options->num_fields_to_write[section_id][ptype] =
-          ptype_num_fields_to_write[ptype];
-    }
-  } /* Ends loop over sections, for different output classes */
-
-  /* Add field numbers for (possible) implicit `Default` output class */
-  if (!have_default) {
-    const int default_id = output_options->select_output->sectionCount;
-    for (int ptype = 0; ptype < swift_type_count; ptype++)
-      output_options->num_fields_to_write[default_id][ptype] =
-          ptype_num_fields_total[ptype];
-  }
-}
-
-/**
- * @brief Write the output field parameters file
- *
- * @param filename The file to write.
- * @param with_cosmology Use cosmological name variant?
- */
-void io_write_output_field_parameter(const char* filename, int with_cosmology) {
-
-  FILE* file = fopen(filename, "w");
-  if (file == NULL) error("Error opening file '%s'", filename);
-
-  /* Create a fake unit system for the snapshots */
-  struct unit_system snapshot_units;
-  units_init_cgs(&snapshot_units);
-
-  /* Loop over all particle types */
-  fprintf(file, "Default:\n");
-  for (int ptype = 0; ptype < swift_type_count; ptype++) {
-
-    struct io_props list[100];
-    int num_fields = get_ptype_fields(ptype, list, with_cosmology,
-                                      /*with_fof=*/1, /*with_stf=*/1);
-
-    if (num_fields == 0) continue;
-
-    /* Output a header for that particle type */
-    fprintf(file, "  # Particle Type %s\n", part_type_names[ptype]);
-
-    /* Write all the fields of this particle type */
-    for (int i = 0; i < num_fields; ++i) {
-
-      char unit_buffer[FIELD_BUFFER_SIZE] = {0};
-      units_cgs_conversion_string(unit_buffer, &snapshot_units, list[i].units,
-                                  list[i].scale_factor_exponent);
-
-      /* Need to buffer with a maximal size - otherwise we can't read in again
-       * because comments are too long */
-      char comment_write_buffer[PARSER_MAX_LINE_SIZE / 2];
-
-      sprintf(comment_write_buffer, "%.*s", PARSER_MAX_LINE_SIZE / 2 - 1,
-              list[i].description);
-
-      /* If our string is too long, replace the last few characters (before
-       * \0) with ... for 'fancy printing' */
-      if (strlen(comment_write_buffer) > PARSER_MAX_LINE_SIZE / 2 - 3) {
-        strcpy(&comment_write_buffer[PARSER_MAX_LINE_SIZE / 2 - 4], "...");
-      }
-
-      fprintf(file, "  %s_%s: %s  # %s : %s\n", list[i].name,
-              part_type_names[ptype], "on", comment_write_buffer, unit_buffer);
-    }
-
-    fprintf(file, "\n");
-  }
-
-  fclose(file);
-
-  printf(
-      "List of valid ouput fields for the particle in snapshots dumped in "
-      "'%s'.\n",
-      filename);
-}
-
 /**
  * @brief Create the subdirectory for snapshots if the user demanded one.
  *
@@ -2831,114 +1408,6 @@ void io_get_snapshot_filename(char filename[1024], char xmf_filename[1024],
     sprintf(xmf_filename, "%s.xmf", basename);
   }
 }
-
-/**
- * @brief Return the number and names of all output fields of a given ptype.
- *
- * @param ptype The index of the particle type under consideration.
- * @param list An io_props list that will hold the individual fields.
- * @param with_cosmology Use cosmological name variant?
- * @param with_fof Include FoF related fields?
- * @param with_stf Include STF related fields?
- *
- * @return The total number of fields that can be written for the ptype.
- */
-int get_ptype_fields(const int ptype, struct io_props* list,
-                     const int with_cosmology, const int with_fof,
-                     const int with_stf) {
-
-  int num_fields = 0;
-
-  switch (ptype) {
-
-    case swift_type_gas:
-      hydro_write_particles(NULL, NULL, list, &num_fields);
-      num_fields += chemistry_write_particles(NULL, NULL, list + num_fields,
-                                              with_cosmology);
-      num_fields +=
-          cooling_write_particles(NULL, NULL, list + num_fields, NULL);
-      num_fields += tracers_write_particles(NULL, NULL, list + num_fields,
-                                            with_cosmology);
-      num_fields +=
-          star_formation_write_particles(NULL, NULL, list + num_fields);
-      if (with_fof)
-        num_fields += fof_write_parts(NULL, NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_parts(NULL, NULL, list + num_fields);
-      num_fields += rt_write_particles(NULL, list + num_fields);
-      break;
-
-    case swift_type_dark_matter:
-      darkmatter_write_particles(NULL, list, &num_fields);
-      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
-      break;
-
-    case swift_type_dark_matter_background:
-      darkmatter_write_particles(NULL, list, &num_fields);
-      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
-      break;
-
-    case swift_type_stars:
-      stars_write_particles(NULL, list, &num_fields, with_cosmology);
-      num_fields += chemistry_write_sparticles(NULL, list + num_fields);
-      num_fields +=
-          tracers_write_sparticles(NULL, list + num_fields, with_cosmology);
-      num_fields += star_formation_write_sparticles(NULL, list + num_fields);
-      if (with_fof) num_fields += fof_write_sparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_sparts(NULL, list + num_fields);
-      num_fields += rt_write_stars(NULL, list + num_fields);
-      break;
-
-    case swift_type_sink:
-      sink_write_particles(NULL, list, &num_fields, with_cosmology);
-      break;
-
-    case swift_type_black_hole:
-      black_holes_write_particles(NULL, list, &num_fields, with_cosmology);
-      num_fields += chemistry_write_bparticles(NULL, list + num_fields);
-      if (with_fof) num_fields += fof_write_bparts(NULL, list + num_fields);
-      if (with_stf)
-        num_fields += velociraptor_write_bparts(NULL, list + num_fields);
-      break;
-
-    default:
-      error("Particle Type %d not yet supported. Aborting", ptype);
-  }
-
-  return num_fields;
-}
-
-/**
- * @brief Return the particle type code of a select_output parameter
- *
- * @param name The name of the parameter under consideration.
- *
- * @return The (integer) particle type of the parameter.
- */
-int get_param_ptype(const char* name) {
-
-  const int name_len = strlen(name);
-
-  for (int ptype = 0; ptype < swift_type_count; ptype++) {
-    const int ptype_name_len = strlen(part_type_names[ptype]);
-    if (name_len >= ptype_name_len &&
-        strcmp(&name[name_len - ptype_name_len], part_type_names[ptype]) == 0)
-      return ptype;
-  }
-
-  /* If we get here, we could not match the name, so something's gone wrong. */
-  error("Could not determine the particle type for parameter '%s'.", name);
-
-  /* We can never get here, but the compiler may complain if we don't return
-   * an int after promising to do so... */
-  return -1;
-}
-
 /**
  * @brief Set all ParticleIDs for each gpart to 1.
  *
@@ -2950,6 +1419,6 @@ int get_param_ptype(const char* name) {
  * @param gparts The array of loaded gparts.
  * @param Ngparts Number of loaded gparts.
  */
-void set_ids_to_one(struct gpart* gparts, const size_t Ngparts) {
+void io_set_ids_to_one(struct gpart* gparts, const size_t Ngparts) {
   for (size_t i = 0; i < Ngparts; i++) gparts[i].id_or_neg_offset = 1;
 }
diff --git a/src/common_io.h b/src/common_io.h
index e8fc1fc4f68fbdcae027974593b6d4f124cd1fc7..95f00cf6c3426cb99b6c1d045dd265302cbad468 100644
--- a/src/common_io.h
+++ b/src/common_io.h
@@ -193,10 +193,6 @@ void io_get_snapshot_filename(char filename[1024], char xmf_filename[1024],
                               const int stf_count, const int snap_count,
                               const char* subdir, const char* basename);
 
-int get_ptype_fields(const int ptype, struct io_props* list,
-                     const int with_cosmology, const int with_fof,
-                     const int with_stf);
-int get_param_ptype(const char* name);
-void set_ids_to_one(struct gpart* gparts, const size_t Ngparts);
+void io_set_ids_to_one(struct gpart* gparts, const size_t Ngparts);
 
 #endif /* SWIFT_COMMON_IO_H */
diff --git a/src/common_io_cells.c b/src/common_io_cells.c
new file mode 100644
index 0000000000000000000000000000000000000000..31bc6521e80cf5ae4379e3df7f96e474d0e172b0
--- /dev/null
+++ b/src/common_io_cells.c
@@ -0,0 +1,491 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2020 Matthieu Schaller (schaller@strw.leidenuniv.nl)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "cell.h"
+#include "timeline.h"
+#include "units.h"
+
+#if defined(HAVE_HDF5)
+
+#include <hdf5.h>
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+static long long cell_count_non_inhibited_gas(const struct cell* c) {
+  const int total_count = c->hydro.count;
+  struct part* parts = c->hydro.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((parts[i].time_bin != time_bin_inhibited) &&
+        (parts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_dark_matter(const struct cell* c) {
+  const int total_count = c->grav.count;
+  struct gpart* gparts = c->grav.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((gparts[i].time_bin != time_bin_inhibited) &&
+        (gparts[i].time_bin != time_bin_not_created) &&
+        (gparts[i].type == swift_type_dark_matter)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_background_dark_matter(
+    const struct cell* c) {
+  const int total_count = c->grav.count;
+  struct gpart* gparts = c->grav.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((gparts[i].time_bin != time_bin_inhibited) &&
+        (gparts[i].time_bin != time_bin_not_created) &&
+        (gparts[i].type == swift_type_dark_matter_background)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_stars(const struct cell* c) {
+  const int total_count = c->stars.count;
+  struct spart* sparts = c->stars.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((sparts[i].time_bin != time_bin_inhibited) &&
+        (sparts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_black_holes(const struct cell* c) {
+  const int total_count = c->black_holes.count;
+  struct bpart* bparts = c->black_holes.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((bparts[i].time_bin != time_bin_inhibited) &&
+        (bparts[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+static long long cell_count_non_inhibited_sinks(const struct cell* c) {
+  const int total_count = c->sinks.count;
+  struct sink* sinks = c->sinks.parts;
+  long long count = 0;
+  for (int i = 0; i < total_count; ++i) {
+    if ((sinks[i].time_bin != time_bin_inhibited) &&
+        (sinks[i].time_bin != time_bin_not_created)) {
+      ++count;
+    }
+  }
+  return count;
+}
+
+/**
+ * @brief Write a single 1D array to a hdf5 group.
+ *
+ * This creates a simple Nx1 array with a chunk size of 1024x1.
+ * The Fletcher-32 filter is applied to the array.
+ *
+ * @param h_grp The open hdf5 group.
+ * @param n The number of elements in the array.
+ * @param array The data to write.
+ * @param type The type of the data to write.
+ * @param name The name of the array.
+ * @param array_content The name of the parent group (only used for error
+ * messages).
+ */
+void io_write_array(hid_t h_grp, const int n, const void* array,
+                    const enum IO_DATA_TYPE type, const char* name,
+                    const char* array_content) {
+
+  /* Create memory space */
+  const hsize_t shape[2] = {(hsize_t)n, 1};
+  hid_t h_space = H5Screate(H5S_SIMPLE);
+  if (h_space < 0)
+    error("Error while creating data space for %s %s", name, array_content);
+  hid_t h_err = H5Sset_extent_simple(h_space, 1, shape, shape);
+  if (h_err < 0)
+    error("Error while changing shape of %s %s data space.", name,
+          array_content);
+
+  /* Dataset type */
+  hid_t h_type = H5Tcopy(io_hdf5_type(type));
+
+  const hsize_t chunk[2] = {(1024 > n ? n : 1024), 1};
+  hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
+  h_err = H5Pset_chunk(h_prop, 1, chunk);
+  if (h_err < 0)
+    error("Error while setting chunk shapes of %s %s data space.", name,
+          array_content);
+
+  /* Impose check-sum to verify data corruption */
+  h_err = H5Pset_fletcher32(h_prop);
+  if (h_err < 0)
+    error("Error while setting check-sum filter on %s %s data space.", name,
+          array_content);
+
+  /* Write */
+  hid_t h_data =
+      H5Dcreate(h_grp, name, h_type, h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
+  if (h_data < 0)
+    error("Error while creating dataspace for %s %s.", name, array_content);
+  h_err = H5Dwrite(h_data, io_hdf5_type(type), h_space, H5S_ALL, H5P_DEFAULT,
+                   array);
+  if (h_err < 0) error("Error while writing %s %s.", name, array_content);
+  H5Tclose(h_type);
+  H5Dclose(h_data);
+  H5Pclose(h_prop);
+  H5Sclose(h_space);
+}
+
+void io_write_cell_offsets(hid_t h_grp, const int cdim[3], const double dim[3],
+                           const struct cell* cells_top, const int nr_cells,
+                           const double width[3], const int nodeID,
+                           const int distributed,
+                           const long long global_counts[swift_type_count],
+                           const long long global_offsets[swift_type_count],
+                           const int num_fields[swift_type_count],
+                           const struct unit_system* internal_units,
+                           const struct unit_system* snapshot_units) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (distributed) {
+    if (global_offsets[0] != 0 || global_offsets[1] != 0 ||
+        global_offsets[2] != 0 || global_offsets[3] != 0 ||
+        global_offsets[4] != 0 || global_offsets[5] != 0)
+      error("Global offset non-zero in the distributed io case!");
+  }
+#endif
+
+  /* Abort if we don't have any cells yet (i.e. haven't constructed the space)
+   */
+  if (nr_cells == 0) return;
+
+  double cell_width[3] = {width[0], width[1], width[2]};
+
+  /* Temporary memory for the cell-by-cell information */
+  double* centres = NULL;
+  centres = (double*)malloc(3 * nr_cells * sizeof(double));
+
+  /* Temporary memory for the cell files ID */
+  int* files = NULL;
+  files = (int*)malloc(nr_cells * sizeof(int));
+
+  /* Count of particles in each cell */
+  long long *count_part = NULL, *count_gpart = NULL,
+            *count_background_gpart = NULL, *count_spart = NULL,
+            *count_bpart = NULL, *count_sink = NULL;
+  count_part = (long long*)malloc(nr_cells * sizeof(long long));
+  count_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_spart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_bpart = (long long*)malloc(nr_cells * sizeof(long long));
+  count_sink = (long long*)malloc(nr_cells * sizeof(long long));
+
+  /* Global offsets of particles in each cell */
+  long long *offset_part = NULL, *offset_gpart = NULL,
+            *offset_background_gpart = NULL, *offset_spart = NULL,
+            *offset_bpart = NULL, *offset_sink = NULL;
+  offset_part = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_background_gpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_spart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_bpart = (long long*)malloc(nr_cells * sizeof(long long));
+  offset_sink = (long long*)malloc(nr_cells * sizeof(long long));
+
+  /* Offsets of the 0^th element */
+  offset_part[0] = 0;
+  offset_gpart[0] = 0;
+  offset_background_gpart[0] = 0;
+  offset_spart[0] = 0;
+  offset_bpart[0] = 0;
+  offset_sink[0] = 0;
+
+  /* Collect the cell information of *local* cells */
+  long long local_offset_part = 0;
+  long long local_offset_gpart = 0;
+  long long local_offset_background_gpart = 0;
+  long long local_offset_spart = 0;
+  long long local_offset_bpart = 0;
+  long long local_offset_sink = 0;
+  for (int i = 0; i < nr_cells; ++i) {
+
+    /* Store in which file this cell will be found */
+    if (distributed) {
+      files[i] = cells_top[i].nodeID;
+    } else {
+      files[i] = 0;
+    }
+
+    /* Is the cell on this node (i.e. we have full information */
+    if (cells_top[i].nodeID == nodeID) {
+
+      /* Centre of each cell */
+      centres[i * 3 + 0] = cells_top[i].loc[0] + cell_width[0] * 0.5;
+      centres[i * 3 + 1] = cells_top[i].loc[1] + cell_width[1] * 0.5;
+      centres[i * 3 + 2] = cells_top[i].loc[2] + cell_width[2] * 0.5;
+
+      /* Finish by box wrapping to match what is done to the particles */
+      centres[i * 3 + 0] = box_wrap(centres[i * 3 + 0], 0.0, dim[0]);
+      centres[i * 3 + 1] = box_wrap(centres[i * 3 + 1], 0.0, dim[1]);
+      centres[i * 3 + 2] = box_wrap(centres[i * 3 + 2], 0.0, dim[2]);
+
+      /* Count real particles that will be written */
+      count_part[i] = cell_count_non_inhibited_gas(&cells_top[i]);
+      count_gpart[i] = cell_count_non_inhibited_dark_matter(&cells_top[i]);
+      count_background_gpart[i] =
+          cell_count_non_inhibited_background_dark_matter(&cells_top[i]);
+      count_spart[i] = cell_count_non_inhibited_stars(&cells_top[i]);
+      count_bpart[i] = cell_count_non_inhibited_black_holes(&cells_top[i]);
+      count_sink[i] = cell_count_non_inhibited_sinks(&cells_top[i]);
+
+      /* Offsets including the global offset of all particles on this MPI rank
+       * Note that in the distributed case, the global offsets are 0 such that
+       * we actually compute the offset in the file written by this rank. */
+      offset_part[i] = local_offset_part + global_offsets[swift_type_gas];
+      offset_gpart[i] =
+          local_offset_gpart + global_offsets[swift_type_dark_matter];
+      offset_background_gpart[i] =
+          local_offset_background_gpart +
+          global_offsets[swift_type_dark_matter_background];
+      offset_spart[i] = local_offset_spart + global_offsets[swift_type_stars];
+      offset_bpart[i] =
+          local_offset_bpart + global_offsets[swift_type_black_hole];
+      offset_sink[i] = local_offset_sink + global_offsets[swift_type_sink];
+
+      local_offset_part += count_part[i];
+      local_offset_gpart += count_gpart[i];
+      local_offset_background_gpart += count_background_gpart[i];
+      local_offset_spart += count_spart[i];
+      local_offset_bpart += count_bpart[i];
+      local_offset_sink += count_sink[i];
+
+    } else {
+
+      /* Just zero everything for the foregin cells */
+
+      centres[i * 3 + 0] = 0.;
+      centres[i * 3 + 1] = 0.;
+      centres[i * 3 + 2] = 0.;
+
+      count_part[i] = 0;
+      count_gpart[i] = 0;
+      count_background_gpart[i] = 0;
+      count_spart[i] = 0;
+      count_bpart[i] = 0;
+      count_sink[i] = 0;
+
+      offset_part[i] = 0;
+      offset_gpart[i] = 0;
+      offset_background_gpart[i] = 0;
+      offset_spart[i] = 0;
+      offset_bpart[i] = 0;
+      offset_sink[i] = 0;
+    }
+  }
+
+#ifdef WITH_MPI
+  /* Now, reduce all the arrays. Note that we use a bit-wise OR here. This
+     is safe as we made sure only local cells have non-zero values. */
+  MPI_Allreduce(MPI_IN_PLACE, count_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_gpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_background_gpart, nr_cells,
+                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_spart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, count_bpart, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+
+  MPI_Allreduce(MPI_IN_PLACE, offset_part, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_gpart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_background_gpart, nr_cells,
+                MPI_LONG_LONG_INT, MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_sink, nr_cells, MPI_LONG_LONG_INT, MPI_BOR,
+                MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_spart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+  MPI_Allreduce(MPI_IN_PLACE, offset_bpart, nr_cells, MPI_LONG_LONG_INT,
+                MPI_BOR, MPI_COMM_WORLD);
+
+  /* For the centres we use a sum as MPI does not like bit-wise operations
+     on floating point numbers */
+  MPI_Allreduce(MPI_IN_PLACE, centres, 3 * nr_cells, MPI_DOUBLE, MPI_SUM,
+                MPI_COMM_WORLD);
+#endif
+
+  /* When writing a single file, only rank 0 writes the meta-data */
+  if ((distributed) || (!distributed && nodeID == 0)) {
+
+    /* Unit conversion if necessary */
+    const double factor = units_conversion_factor(
+        internal_units, snapshot_units, UNIT_CONV_LENGTH);
+    if (factor != 1.) {
+
+      /* Convert the cell centres */
+      for (int i = 0; i < nr_cells; ++i) {
+        centres[i * 3 + 0] *= factor;
+        centres[i * 3 + 1] *= factor;
+        centres[i * 3 + 2] *= factor;
+      }
+
+      /* Convert the cell widths */
+      cell_width[0] *= factor;
+      cell_width[1] *= factor;
+      cell_width[2] *= factor;
+    }
+
+    /* Write some meta-information first */
+    hid_t h_subgrp =
+        H5Gcreate(h_grp, "Meta-data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_subgrp < 0) error("Error while creating meta-data sub-group");
+    io_write_attribute(h_subgrp, "nr_cells", INT, &nr_cells, 1);
+    io_write_attribute(h_subgrp, "size", DOUBLE, cell_width, 3);
+    io_write_attribute(h_subgrp, "dimension", INT, cdim, 3);
+    H5Gclose(h_subgrp);
+
+    /* Write the centres to the group */
+    hsize_t shape[2] = {(hsize_t)nr_cells, 3};
+    hid_t h_space = H5Screate(H5S_SIMPLE);
+    if (h_space < 0) error("Error while creating data space for cell centres");
+    hid_t h_err = H5Sset_extent_simple(h_space, 2, shape, shape);
+    if (h_err < 0)
+      error("Error while changing shape of gas offsets data space.");
+    hid_t h_data = H5Dcreate(h_grp, "Centres", io_hdf5_type(DOUBLE), h_space,
+                             H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_data < 0) error("Error while creating dataspace for gas offsets.");
+    h_err = H5Dwrite(h_data, io_hdf5_type(DOUBLE), h_space, H5S_ALL,
+                     H5P_DEFAULT, centres);
+    if (h_err < 0) error("Error while writing centres.");
+    H5Dclose(h_data);
+    H5Sclose(h_space);
+
+    /* Group containing the offsets and counts for each particle type */
+    hid_t h_grp_offsets = H5Gcreate(h_grp, "OffsetsInFile", H5P_DEFAULT,
+                                    H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_offsets < 0) error("Error while creating offsets sub-group");
+    hid_t h_grp_files =
+        H5Gcreate(h_grp, "Files", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_files < 0) error("Error while creating filess sub-group");
+    hid_t h_grp_counts =
+        H5Gcreate(h_grp, "Counts", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
+    if (h_grp_counts < 0) error("Error while creating counts sub-group");
+
+    if (global_counts[swift_type_gas] > 0 && num_fields[swift_type_gas] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType0", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_part, LONGLONG,
+                     "PartType0", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_part, LONGLONG, "PartType0",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_dark_matter] > 0 &&
+        num_fields[swift_type_dark_matter] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType1", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_gpart, LONGLONG,
+                     "PartType1", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_gpart, LONGLONG, "PartType1",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_dark_matter_background] > 0 &&
+        num_fields[swift_type_dark_matter_background] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType2", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_background_gpart, LONGLONG,
+                     "PartType2", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_background_gpart, LONGLONG,
+                     "PartType2", "counts");
+    }
+
+    if (global_counts[swift_type_sink] > 0 && num_fields[swift_type_sink] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType3", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_sink, LONGLONG,
+                     "PartType3", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_sink, LONGLONG, "PartType3",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_stars] > 0 &&
+        num_fields[swift_type_stars] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType4", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_spart, LONGLONG,
+                     "PartType4", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_spart, LONGLONG, "PartType4",
+                     "counts");
+    }
+
+    if (global_counts[swift_type_black_hole] > 0 &&
+        num_fields[swift_type_black_hole] > 0) {
+      io_write_array(h_grp_files, nr_cells, files, INT, "PartType5", "files");
+      io_write_array(h_grp_offsets, nr_cells, offset_bpart, LONGLONG,
+                     "PartType5", "offsets");
+      io_write_array(h_grp_counts, nr_cells, count_bpart, LONGLONG, "PartType5",
+                     "counts");
+    }
+
+    H5Gclose(h_grp_offsets);
+    H5Gclose(h_grp_files);
+    H5Gclose(h_grp_counts);
+  }
+
+  /* Free everything we allocated */
+  free(centres);
+  free(files);
+  free(count_part);
+  free(count_gpart);
+  free(count_background_gpart);
+  free(count_spart);
+  free(count_bpart);
+  free(count_sink);
+  free(offset_part);
+  free(offset_gpart);
+  free(offset_background_gpart);
+  free(offset_spart);
+  free(offset_bpart);
+  free(offset_sink);
+}
+
+#endif /* HAVE_HDF5 */
diff --git a/src/common_io_copy.c b/src/common_io_copy.c
new file mode 100644
index 0000000000000000000000000000000000000000..e7e9f157726591a8e19c15c51df947e5617132ca
--- /dev/null
+++ b/src/common_io_copy.c
@@ -0,0 +1,753 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "engine.h"
+#include "io_properties.h"
+#include "threadpool.h"
+
+/**
+ * @brief Mapper function to copy #part or #gpart fields into a buffer.
+ */
+void io_copy_mapper(void* restrict temp, int N, void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const size_t typeSize = io_sizeof_type(props.type);
+  const size_t copySize = typeSize * props.dimension;
+
+  /* How far are we with this chunk? */
+  char* restrict temp_c = (char*)temp;
+  const ptrdiff_t delta = (temp_c - props.start_temp_c) / copySize;
+
+  for (int k = 0; k < N; k++) {
+    memcpy(&temp_c[k * copySize], props.field + (delta + k) * props.partSize,
+           copySize);
+  }
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_part_f_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_f(e, parts + delta + i, xparts + delta + i,
+                         &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_part_i_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_i(e, parts + delta + i, xparts + delta + i,
+                         &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_part_d_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_d(e, parts + delta + i, xparts + delta + i,
+                         &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #part into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_part_l_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct part* restrict parts = props.parts;
+  const struct xpart* restrict xparts = props.xparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_part_l(e, parts + delta + i, xparts + delta + i,
+                         &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_gpart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_f(e, gparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_gpart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_i(e, gparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_gpart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_d(e, gparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #gpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_gpart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct gpart* restrict gparts = props.gparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_gpart_l(e, gparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_spart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_f(e, sparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_spart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_i(e, sparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_spart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_d(e, sparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #spart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_spart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct spart* restrict sparts = props.sparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_spart_l(e, sparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_bpart_f_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_f(e, bparts + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_bpart_i_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_i(e, bparts + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_bpart_d_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_d(e, bparts + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #bpart into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_bpart_l_mapper(void* restrict temp, int N,
+                               void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct bpart* restrict bparts = props.bparts;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_bpart_l(e, bparts + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of floats using a
+ * conversion function.
+ */
+void io_convert_sink_f_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  float* restrict temp_f = (float*)temp;
+  const ptrdiff_t delta = (temp_f - props.start_temp_f) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_f(e, sinks + delta + i, &temp_f[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of ints using a
+ * conversion function.
+ */
+void io_convert_sink_i_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  int* restrict temp_i = (int*)temp;
+  const ptrdiff_t delta = (temp_i - props.start_temp_i) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_i(e, sinks + delta + i, &temp_i[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_sink_d_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  double* restrict temp_d = (double*)temp;
+  const ptrdiff_t delta = (temp_d - props.start_temp_d) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_d(e, sinks + delta + i, &temp_d[i * dim]);
+}
+
+/**
+ * @brief Mapper function to copy #sink into a buffer of doubles using a
+ * conversion function.
+ */
+void io_convert_sink_l_mapper(void* restrict temp, int N,
+                              void* restrict extra_data) {
+
+  const struct io_props props = *((const struct io_props*)extra_data);
+  const struct sink* restrict sinks = props.sinks;
+  const struct engine* e = props.e;
+  const size_t dim = props.dimension;
+
+  /* How far are we with this chunk? */
+  long long* restrict temp_l = (long long*)temp;
+  const ptrdiff_t delta = (temp_l - props.start_temp_l) / dim;
+
+  for (int i = 0; i < N; i++)
+    props.convert_sink_l(e, sinks + delta + i, &temp_l[i * dim]);
+}
+
+/**
+ * @brief Copy the particle data into a temporary buffer ready for i/o.
+ *
+ * @param temp The buffer to be filled. Must be allocated and aligned properly.
+ * @param e The #engine.
+ * @param props The #io_props corresponding to the particle field we are
+ * copying.
+ * @param N The number of particles to copy
+ * @param internal_units The system of units used internally.
+ * @param snapshot_units The system of units used for the snapshots.
+ */
+void io_copy_temp_buffer(void* temp, const struct engine* e,
+                         struct io_props props, size_t N,
+                         const struct unit_system* internal_units,
+                         const struct unit_system* snapshot_units) {
+
+  const size_t typeSize = io_sizeof_type(props.type);
+  const size_t copySize = typeSize * props.dimension;
+  const size_t num_elements = N * props.dimension;
+
+  /* Copy particle data to temporary buffer */
+  if (props.conversion == 0) { /* No conversion */
+
+    /* Prepare some parameters */
+    char* temp_c = (char*)temp;
+    props.start_temp_c = temp_c;
+
+    /* Copy the whole thing into a buffer */
+    threadpool_map((struct threadpool*)&e->threadpool, io_copy_mapper, temp_c,
+                   N, copySize, threadpool_auto_chunk_size, (void*)&props);
+
+  } else { /* Converting particle to data */
+
+    if (props.convert_part_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_part_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_part_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_gpart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_gpart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_spart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_spart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_sink_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_sink_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_f != NULL) {
+
+      /* Prepare some parameters */
+      float* temp_f = (float*)temp;
+      props.start_temp_f = (float*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_f_mapper, temp_f, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_i != NULL) {
+
+      /* Prepare some parameters */
+      int* temp_i = (int*)temp;
+      props.start_temp_i = (int*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_i_mapper, temp_i, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_d != NULL) {
+
+      /* Prepare some parameters */
+      double* temp_d = (double*)temp;
+      props.start_temp_d = (double*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_d_mapper, temp_d, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else if (props.convert_bpart_l != NULL) {
+
+      /* Prepare some parameters */
+      long long* temp_l = (long long*)temp;
+      props.start_temp_l = (long long*)temp;
+      props.e = e;
+
+      /* Copy the whole thing into a buffer */
+      threadpool_map((struct threadpool*)&e->threadpool,
+                     io_convert_bpart_l_mapper, temp_l, N, copySize,
+                     threadpool_auto_chunk_size, (void*)&props);
+
+    } else {
+      error("Missing conversion function");
+    }
+  }
+
+  /* Unit conversion if necessary */
+  const double factor =
+      units_conversion_factor(internal_units, snapshot_units, props.units);
+  if (factor != 1.) {
+
+    /* message("Converting ! factor=%e", factor); */
+
+    if (io_is_double_precision(props.type)) {
+      swift_declare_aligned_ptr(double, temp_d, (double*)temp,
+                                IO_BUFFER_ALIGNMENT);
+      for (size_t i = 0; i < num_elements; ++i) temp_d[i] *= factor;
+    } else {
+      swift_declare_aligned_ptr(float, temp_f, (float*)temp,
+                                IO_BUFFER_ALIGNMENT);
+      for (size_t i = 0; i < num_elements; ++i) temp_f[i] *= factor;
+    }
+  }
+}
diff --git a/src/common_io_fields.c b/src/common_io_fields.c
new file mode 100644
index 0000000000000000000000000000000000000000..1eb9acfd75268ee83a7d865bd7c55821abc7f2bf
--- /dev/null
+++ b/src/common_io_fields.c
@@ -0,0 +1,389 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2017 Matthieu Schaller (matthieu.schaller@durham.ac.uk).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* This object's header. */
+#include "common_io.h"
+
+/* Local includes. */
+#include "error.h"
+#include "units.h"
+
+/* I/O functions of each sub-module */
+#include "black_holes_io.h"
+#include "chemistry_io.h"
+#include "cooling_io.h"
+#include "fof_io.h"
+#include "gravity_io.h"
+#include "hydro_io.h"
+#include "rt_io.h"
+#include "sink_io.h"
+#include "star_formation_io.h"
+#include "stars_io.h"
+#include "tracers_io.h"
+#include "velociraptor_io.h"
+
+/* Some standard headers. */
+#include <string.h>
+
+/**
+ * @brief Return the particle type code of a select_output parameter
+ *
+ * @param name The name of the parameter under consideration.
+ *
+ * @return The (integer) particle type of the parameter.
+ */
+int io_get_param_ptype(const char* name) {
+
+  const int name_len = strlen(name);
+
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
+    const int ptype_name_len = strlen(part_type_names[ptype]);
+    if (name_len >= ptype_name_len &&
+        strcmp(&name[name_len - ptype_name_len], part_type_names[ptype]) == 0)
+      return ptype;
+  }
+
+  /* If we get here, we could not match the name, so something's gone wrong. */
+  error("Could not determine the particle type for parameter '%s'.", name);
+
+  /* We can never get here, but the compiler may complain if we don't return
+   * an int after promising to do so... */
+  return -1;
+}
+
+/**
+ * @brief Return the number and names of all output fields of a given ptype.
+ *
+ * @param ptype The index of the particle type under consideration.
+ * @param list An io_props list that will hold the individual fields.
+ * @param with_cosmology Use cosmological name variant?
+ * @param with_fof Include FoF related fields?
+ * @param with_stf Include STF related fields?
+ *
+ * @return The total number of fields that can be written for the ptype.
+ */
+int io_get_ptype_fields(const int ptype, struct io_props* list,
+                        const int with_cosmology, const int with_fof,
+                        const int with_stf) {
+
+  int num_fields = 0;
+
+  switch (ptype) {
+
+    case swift_type_gas:
+      hydro_write_particles(NULL, NULL, list, &num_fields);
+      num_fields += chemistry_write_particles(NULL, NULL, list + num_fields,
+                                              with_cosmology);
+      num_fields +=
+          cooling_write_particles(NULL, NULL, list + num_fields, NULL);
+      num_fields += tracers_write_particles(NULL, NULL, list + num_fields,
+                                            with_cosmology);
+      num_fields +=
+          star_formation_write_particles(NULL, NULL, list + num_fields);
+      if (with_fof)
+        num_fields += fof_write_parts(NULL, NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_parts(NULL, NULL, list + num_fields);
+      num_fields += rt_write_particles(NULL, list + num_fields);
+      break;
+
+    case swift_type_dark_matter:
+      darkmatter_write_particles(NULL, list, &num_fields);
+      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
+      break;
+
+    case swift_type_dark_matter_background:
+      darkmatter_write_particles(NULL, list, &num_fields);
+      if (with_fof) num_fields += fof_write_gparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_gparts(NULL, list + num_fields);
+      break;
+
+    case swift_type_stars:
+      stars_write_particles(NULL, list, &num_fields, with_cosmology);
+      num_fields += chemistry_write_sparticles(NULL, list + num_fields);
+      num_fields +=
+          tracers_write_sparticles(NULL, list + num_fields, with_cosmology);
+      num_fields += star_formation_write_sparticles(NULL, list + num_fields);
+      if (with_fof) num_fields += fof_write_sparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_sparts(NULL, list + num_fields);
+      num_fields += rt_write_stars(NULL, list + num_fields);
+      break;
+
+    case swift_type_sink:
+      sink_write_particles(NULL, list, &num_fields, with_cosmology);
+      break;
+
+    case swift_type_black_hole:
+      black_holes_write_particles(NULL, list, &num_fields, with_cosmology);
+      num_fields += chemistry_write_bparticles(NULL, list + num_fields);
+      if (with_fof) num_fields += fof_write_bparts(NULL, list + num_fields);
+      if (with_stf)
+        num_fields += velociraptor_write_bparts(NULL, list + num_fields);
+      break;
+
+    default:
+      error("Particle Type %d not yet supported. Aborting", ptype);
+  }
+
+  return num_fields;
+}
+
+/**
+ * @brief Prepare the output option fields according to the user's choices and
+ * verify that they are valid.
+ *
+ * @param output_options The #output_options for this run
+ * @param with_cosmology Ran with cosmology?
+ * @param with_fof Are we running with on-the-fly Fof?
+ * @param with_stf Are we running with on-the-fly structure finder?
+ */
+void io_prepare_output_fields(struct output_options* output_options,
+                              const int with_cosmology, const int with_fof,
+                              const int with_stf) {
+
+  const int MAX_NUM_PTYPE_FIELDS = 100;
+
+  /* Parameter struct for the output options */
+  struct swift_params* params = output_options->select_output;
+
+  /* Get all possible outputs per particle type */
+  int ptype_num_fields_total[swift_type_count] = {0};
+  struct io_props field_list[swift_type_count][MAX_NUM_PTYPE_FIELDS];
+
+  for (int ptype = 0; ptype < swift_type_count; ptype++)
+    ptype_num_fields_total[ptype] = io_get_ptype_fields(
+        ptype, field_list[ptype], with_cosmology, with_fof, with_stf);
+
+  /* Check for whether we have a `Default` section */
+  int have_default = 0;
+
+  /* Loop over each section, i.e. different class of output */
+  for (int section_id = 0; section_id < params->sectionCount; section_id++) {
+
+    /* Get the name of current (selection) section, without a trailing colon */
+    char section_name[FIELD_BUFFER_SIZE];
+    strcpy(section_name, params->section[section_id].name);
+    section_name[strlen(section_name) - 1] = 0;
+
+    /* Is this the `Default` section? */
+    if (strcmp(section_name, select_output_header_default_name) == 0)
+      have_default = 1;
+
+    /* How many fields should each ptype write by default? */
+    int ptype_num_fields_to_write[swift_type_count];
+
+    /* What is the default writing status for each ptype (on/off)? */
+    int ptype_default_write_status[swift_type_count];
+
+    /* Initialise section-specific writing counters for each particle type.
+     * If default is 'write', then we start from the total to deduct any fields
+     * that are switched off. If the default is 'off', we have to start from
+     * zero and then count upwards for each field that is switched back on. */
+    for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+      /* Internally also verifies that the default level is allowed */
+      const enum lossy_compression_schemes compression_level_current_default =
+          output_options_get_ptype_default_compression(params, section_name,
+                                                       (enum part_type)ptype);
+
+      if (compression_level_current_default == compression_do_not_write) {
+        ptype_default_write_status[ptype] = 0;
+        ptype_num_fields_to_write[ptype] = 0;
+      } else {
+        ptype_default_write_status[ptype] = 1;
+        ptype_num_fields_to_write[ptype] = ptype_num_fields_total[ptype];
+      }
+
+    } /* ends loop over particle types */
+
+    /* Loop over each parameter */
+    for (int param_id = 0; param_id < params->paramCount; param_id++) {
+
+      /* Full name of the parameter to check */
+      const char* param_name = params->data[param_id].name;
+
+      /* Check whether the file still contains the old, now inappropriate
+       * 'SelectOutput' section */
+      if (strstr(param_name, "SelectOutput:") != NULL) {
+        error(
+            "Output selection files no longer require the use of top level "
+            "SelectOutput; see the documentation for changes.");
+      }
+
+      /* Skip if the parameter belongs to another output class or is a
+       * 'Standard' parameter */
+      if (strstr(param_name, section_name) == NULL) continue;
+      if (strstr(param_name, ":Standard_") != NULL) continue;
+
+      /* Get the particle type for current parameter
+       * (raises an error if it could not determine it) */
+      const int param_ptype = io_get_param_ptype(param_name);
+
+      /* Issue a warning if this parameter does not pertain to any of the
+       * known fields from this ptype. */
+      int field_id = 0;
+      char field_name[PARSER_MAX_LINE_SIZE];
+      for (field_id = 0; field_id < ptype_num_fields_total[param_ptype];
+           field_id++) {
+
+        sprintf(field_name, "%s:%.*s_%s", section_name, FIELD_BUFFER_SIZE,
+                field_list[param_ptype][field_id].name,
+                part_type_names[param_ptype]);
+
+        if (strcmp(param_name, field_name) == 0) break;
+      }
+
+      int param_is_known = 0; /* Update below if it is a known one */
+      if (field_id < ptype_num_fields_total[param_ptype])
+        param_is_known = 1;
+      else
+        message(
+            "WARNING: Trying to change behaviour of field '%s' (read from "
+            "'%s') that does not exist. This may be because you are not "
+            "running with all of the physics that you compiled the code with.",
+            param_name, params->fileName);
+
+      /* Perform a correctness check on the _value_ of the parameter */
+      char param_value[FIELD_BUFFER_SIZE];
+      parser_get_param_string(params, param_name, param_value);
+
+      int value_id = 0;
+      for (value_id = 0; value_id < compression_level_count; value_id++)
+        if (strcmp(param_value, lossy_compression_schemes_names[value_id]) == 0)
+          break;
+
+      if (value_id == compression_level_count)
+        error("Choice of output selection parameter %s ('%s') is invalid.",
+              param_name, param_value);
+
+      /* Adjust number of fields to be written for param_ptype, if this field's
+       * status is different from default and it is a known one. */
+      if (param_is_known) {
+        const int is_on =
+            strcmp(param_value,
+                   lossy_compression_schemes_names[compression_do_not_write]) !=
+            0;
+
+        if (is_on && !ptype_default_write_status[param_ptype]) {
+          /* Particle should be written even though default is off:
+           * increase field count */
+          ptype_num_fields_to_write[param_ptype] += 1;
+        }
+        if (!is_on && ptype_default_write_status[param_ptype]) {
+          /* Particle should not be written, even though default is on:
+           * decrease field count */
+          ptype_num_fields_to_write[param_ptype] -= 1;
+        }
+      }
+    } /* ends loop over parameters */
+
+    /* Second loop over ptypes, to write out total number of fields to write */
+    for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+      /* Sanity check: is the number of fields to write non-negative? */
+      if (ptype_num_fields_to_write[ptype] < 0)
+        error(
+            "We seem to have subtracted too many fields for particle "
+            "type %d in output class %s (total to write is %d)",
+            ptype, section_name, ptype_num_fields_to_write[ptype]);
+#endif
+      output_options->num_fields_to_write[section_id][ptype] =
+          ptype_num_fields_to_write[ptype];
+    }
+  } /* Ends loop over sections, for different output classes */
+
+  /* Add field numbers for (possible) implicit `Default` output class */
+  if (!have_default) {
+    const int default_id = output_options->select_output->sectionCount;
+    for (int ptype = 0; ptype < swift_type_count; ptype++)
+      output_options->num_fields_to_write[default_id][ptype] =
+          ptype_num_fields_total[ptype];
+  }
+}
+
+/**
+ * @brief Write the output field parameters file
+ *
+ * @param filename The file to write.
+ * @param with_cosmology Use cosmological name variant?
+ */
+void io_write_output_field_parameter(const char* filename, int with_cosmology) {
+
+  FILE* file = fopen(filename, "w");
+  if (file == NULL) error("Error opening file '%s'", filename);
+
+  /* Create a fake unit system for the snapshots */
+  struct unit_system snapshot_units;
+  units_init_cgs(&snapshot_units);
+
+  /* Loop over all particle types */
+  fprintf(file, "Default:\n");
+  for (int ptype = 0; ptype < swift_type_count; ptype++) {
+
+    struct io_props list[100];
+    int num_fields = io_get_ptype_fields(ptype, list, with_cosmology,
+                                         /*with_fof=*/1, /*with_stf=*/1);
+
+    if (num_fields == 0) continue;
+
+    /* Output a header for that particle type */
+    fprintf(file, "  # Particle Type %s\n", part_type_names[ptype]);
+
+    /* Write all the fields of this particle type */
+    for (int i = 0; i < num_fields; ++i) {
+
+      char unit_buffer[FIELD_BUFFER_SIZE] = {0};
+      units_cgs_conversion_string(unit_buffer, &snapshot_units, list[i].units,
+                                  list[i].scale_factor_exponent);
+
+      /* Need to buffer with a maximal size - otherwise we can't read in again
+       * because comments are too long */
+      char comment_write_buffer[PARSER_MAX_LINE_SIZE / 2];
+
+      sprintf(comment_write_buffer, "%.*s", PARSER_MAX_LINE_SIZE / 2 - 1,
+              list[i].description);
+
+      /* If our string is too long, replace the last few characters (before
+       * \0) with ... for 'fancy printing' */
+      if (strlen(comment_write_buffer) > PARSER_MAX_LINE_SIZE / 2 - 3) {
+        strcpy(&comment_write_buffer[PARSER_MAX_LINE_SIZE / 2 - 4], "...");
+      }
+
+      fprintf(file, "  %s_%s: %s  # %s : %s\n", list[i].name,
+              part_type_names[ptype], "on", comment_write_buffer, unit_buffer);
+    }
+
+    fprintf(file, "\n");
+  }
+
+  fclose(file);
+
+  printf(
+      "List of valid ouput fields for the particle in snapshots dumped in "
+      "'%s'.\n",
+      filename);
+}
diff --git a/src/parallel_io.c b/src/parallel_io.c
index 4e98c24397d51da2e1a43899aa40ab22f43db96d..55b345912da6b57060bf587179089afcf3c88f9a 100644
--- a/src/parallel_io.c
+++ b/src/parallel_io.c
@@ -1046,7 +1046,7 @@ void read_ic_parallel(char* fileName, const struct unit_system* internal_units,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   if (!dry_run && with_gravity) {
 
diff --git a/src/serial_io.c b/src/serial_io.c
index b734d5fa7082b059537887f3260ceafa7c1a22a6..eb801c8f10a21f1336e6dc13897f57925a0905bb 100644
--- a/src/serial_io.c
+++ b/src/serial_io.c
@@ -851,7 +851,7 @@ void read_ic_serial(char* fileName, const struct unit_system* internal_units,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   /* Duplicate the parts for gravity */
   if (!dry_run && with_gravity) {
diff --git a/src/single_io.c b/src/single_io.c
index 3765257f3914a0610763ad611ac2d8aef99c88cb..e0912ba45e53945a3ee98d9d945343df77292b53 100644
--- a/src/single_io.c
+++ b/src/single_io.c
@@ -708,7 +708,7 @@ void read_ic_single(const char* fileName,
   }
 
   /* If we are remapping ParticleIDs later, start by setting them to 1. */
-  if (remap_ids) set_ids_to_one(*gparts, *Ngparts);
+  if (remap_ids) io_set_ids_to_one(*gparts, *Ngparts);
 
   /* Duplicate the parts for gravity */
   if (!dry_run && with_gravity) {