+#define MAX_FTYP_ATOM_SIZE 1048576
+
+typedef struct {
+ uint32_t type;
+ uint32_t header_size;
+ uint64_t size;
+ unsigned char *data;
+} atom_t;
+
+typedef struct {
+ uint64_t moov_atom_size;
+ uint64_t stco_offset_count;
+ uint64_t stco_data_size;
+ int stco_overflow;
+ uint32_t depth;
+} update_chunk_offsets_context_t;
+
+typedef struct {
+ unsigned char *dest;
+ uint64_t original_moov_size;
+ uint64_t new_moov_size;
+} upgrade_stco_context_t;
+
+typedef int (*parse_atoms_callback_t)(void *context, atom_t *atom);
+
+static int parse_atoms(
+ unsigned char *buf,
+ uint64_t size,
+ parse_atoms_callback_t callback,
+ void *context)
+{
+ unsigned char *pos = buf;
+ unsigned char *end = pos + size;
+ atom_t atom;
+ int ret;
+
+ while (end - pos >= ATOM_PREAMBLE_SIZE) {
+ atom.size = BE_32(pos);
+ atom.type = BE_32(pos + 4);
+ pos += ATOM_PREAMBLE_SIZE;
+ atom.header_size = ATOM_PREAMBLE_SIZE;
+
+ switch (atom.size) {
+ case 1:
+ if (end - pos < 8) {
+ fprintf(stderr, "not enough room for 64 bit atom size\n");
+ return -1;
+ }
+
+ atom.size = BE_64(pos);
+ pos += 8;
+ atom.header_size = ATOM_PREAMBLE_SIZE + 8;
+ break;
+
+ case 0:
+ atom.size = ATOM_PREAMBLE_SIZE + end - pos;
+ break;
+ }
+
+ if (atom.size < atom.header_size) {
+ fprintf(stderr, "atom size %"PRIu64" too small\n", atom.size);
+ return -1;
+ }
+
+ atom.size -= atom.header_size;
+
+ if (atom.size > end - pos) {
+ fprintf(stderr, "atom size %"PRIu64" too big\n", atom.size);
+ return -1;
+ }
+
+ atom.data = pos;
+ ret = callback(context, &atom);
+ if (ret < 0) {
+ return ret;
+ }
+
+ pos += atom.size;
+ }
+
+ return 0;
+}
+
+static int update_stco_offsets(update_chunk_offsets_context_t *context, atom_t *atom)
+{
+ uint32_t current_offset;
+ uint32_t offset_count;
+ unsigned char *pos;
+ unsigned char *end;
+
+ printf(" patching stco atom...\n");
+ if (atom->size < 8) {
+ fprintf(stderr, "stco atom size %"PRIu64" too small\n", atom->size);
+ return -1;
+ }
+
+ offset_count = BE_32(atom->data + 4);
+ if (offset_count > (atom->size - 8) / 4) {
+ fprintf(stderr, "stco offset count %"PRIu32" too big\n", offset_count);
+ return -1;
+ }
+
+ context->stco_offset_count += offset_count;
+ context->stco_data_size += atom->size - 8;
+
+ for (pos = atom->data + 8, end = pos + offset_count * 4;
+ pos < end;
+ pos += 4) {
+ current_offset = BE_32(pos);
+ if (current_offset > UINT_MAX - context->moov_atom_size) {
+ context->stco_overflow = 1;
+ }
+ current_offset += context->moov_atom_size;
+ AV_WB32(pos, current_offset);
+ }
+
+ return 0;
+}
+
+static int update_co64_offsets(update_chunk_offsets_context_t *context, atom_t *atom)
+{
+ uint64_t current_offset;
+ uint32_t offset_count;
+ unsigned char *pos;
+ unsigned char *end;
+
+ printf(" patching co64 atom...\n");
+ if (atom->size < 8) {
+ fprintf(stderr, "co64 atom size %"PRIu64" too small\n", atom->size);
+ return -1;
+ }
+
+ offset_count = BE_32(atom->data + 4);
+ if (offset_count > (atom->size - 8) / 8) {
+ fprintf(stderr, "co64 offset count %"PRIu32" too big\n", offset_count);
+ return -1;
+ }
+
+ for (pos = atom->data + 8, end = pos + offset_count * 8;
+ pos < end;
+ pos += 8) {
+ current_offset = BE_64(pos);
+ current_offset += context->moov_atom_size;
+ AV_WB64(pos, current_offset);
+ }
+
+ return 0;
+}
+
+static int update_chunk_offsets_callback(void *ctx, atom_t *atom)
+{
+ update_chunk_offsets_context_t *context = ctx;
+ int ret;
+
+ switch (atom->type) {
+ case STCO_ATOM:
+ return update_stco_offsets(context, atom);
+
+ case CO64_ATOM:
+ return update_co64_offsets(context, atom);
+
+ case MOOV_ATOM:
+ case TRAK_ATOM:
+ case MDIA_ATOM:
+ case MINF_ATOM:
+ case STBL_ATOM:
+ context->depth++;
+ if (context->depth > 10) {
+ fprintf(stderr, "atoms too deeply nested\n");
+ return -1;
+ }
+
+ ret = parse_atoms(
+ atom->data,
+ atom->size,
+ update_chunk_offsets_callback,
+ context);
+ context->depth--;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void set_atom_size(unsigned char *header, uint32_t header_size, uint64_t size)
+{
+ switch (header_size) {
+ case 8:
+ AV_WB32(header, size);
+ break;
+
+ case 16:
+ AV_WB64(header + 8, size);
+ break;
+ }
+}
+
+static void upgrade_stco_atom(upgrade_stco_context_t *context, atom_t *atom)
+{
+ unsigned char *pos;
+ unsigned char *end;
+ uint64_t new_offset;
+ uint32_t offset_count;
+ uint32_t original_offset;
+
+ /* Note: not performing validations since they were performed on the first pass */
+
+ offset_count = BE_32(atom->data + 4);
+
+ /* write the header */
+ memcpy(context->dest, atom->data - atom->header_size, atom->header_size + 8);
+ AV_WB32(context->dest + 4, CO64_ATOM);
+ set_atom_size(context->dest, atom->header_size, atom->header_size + 8 + offset_count * 8);
+ context->dest += atom->header_size + 8;
+
+ /* write the data */
+ for (pos = atom->data + 8, end = pos + offset_count * 4;
+ pos < end;
+ pos += 4) {
+ original_offset = BE_32(pos) - context->original_moov_size;
+ new_offset = (uint64_t)original_offset + context->new_moov_size;
+ AV_WB64(context->dest, new_offset);
+ context->dest += 8;
+ }
+}
+
+static int upgrade_stco_callback(void *ctx, atom_t *atom)
+{
+ upgrade_stco_context_t *context = ctx;
+ unsigned char *start_pos;
+ uint64_t copy_size;
+
+ switch (atom->type) {
+ case STCO_ATOM:
+ upgrade_stco_atom(context, atom);
+ break;
+
+ case MOOV_ATOM:
+ case TRAK_ATOM:
+ case MDIA_ATOM:
+ case MINF_ATOM:
+ case STBL_ATOM:
+ /* write the atom header */
+ memcpy(context->dest, atom->data - atom->header_size, atom->header_size);
+ start_pos = context->dest;
+ context->dest += atom->header_size;
+
+ /* parse internal atoms*/
+ if (parse_atoms(
+ atom->data,
+ atom->size,
+ upgrade_stco_callback,
+ context) < 0) {
+ return -1;
+ }
+
+ /* update the atom size */
+ set_atom_size(start_pos, atom->header_size, context->dest - start_pos);
+ break;
+
+ default:
+ copy_size = atom->header_size + atom->size;
+ memcpy(context->dest, atom->data - atom->header_size, copy_size);
+ context->dest += copy_size;
+ break;
+ }
+
+ return 0;
+}
+
+static int update_moov_atom(
+ unsigned char **moov_atom,
+ uint64_t *moov_atom_size)
+{
+ update_chunk_offsets_context_t update_context = { 0 };
+ upgrade_stco_context_t upgrade_context;
+ unsigned char *new_moov_atom;
+
+ update_context.moov_atom_size = *moov_atom_size;
+
+ if (parse_atoms(
+ *moov_atom,
+ *moov_atom_size,
+ update_chunk_offsets_callback,
+ &update_context) < 0) {
+ return -1;
+ }
+
+ if (!update_context.stco_overflow) {
+ return 0;
+ }
+
+ printf(" upgrading stco atoms to co64...\n");
+ upgrade_context.new_moov_size = *moov_atom_size +
+ update_context.stco_offset_count * 8 -
+ update_context.stco_data_size;
+
+ new_moov_atom = malloc(upgrade_context.new_moov_size);
+ if (new_moov_atom == NULL) {
+ fprintf(stderr, "could not allocate %"PRIu64" bytes for updated moov atom\n",
+ upgrade_context.new_moov_size);
+ return -1;
+ }
+
+ upgrade_context.original_moov_size = *moov_atom_size;
+ upgrade_context.dest = new_moov_atom;
+
+ if (parse_atoms(
+ *moov_atom,
+ *moov_atom_size,
+ upgrade_stco_callback,
+ &upgrade_context) < 0) {
+ free(new_moov_atom);
+ return -1;
+ }
+
+ free(*moov_atom);
+ *moov_atom = new_moov_atom;
+ *moov_atom_size = upgrade_context.new_moov_size;
+
+ if (upgrade_context.dest != *moov_atom + *moov_atom_size) {
+ fprintf(stderr, "unexpected - wrong number of moov bytes written\n");
+ return -1;
+ }
+
+ return 0;
+}