/**************************************************************
*
* cr3.h (C-Munipack project)
* Copyright (C) 2021 David Motl, dmotl@volny.cz
*
* This code is adaptation of the code include in LibRaw version 0.21
*
* I removed everything that is not needed to decode CR3 files and
* I made the code compatible with ANSI C. The memory allocation uses
* the cmpack* routines in order to track memory leaks.
*
* I release this under the same license as the LibRaw, see below.
*
***************************************************************/

/*
Libraw licence

Copyright (C) 2018-2019 Alexey Danilchenko
Copyright (C) 2019 Alex Tutubalin, LibRaw LLC

LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

 */
#include "cr3.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#define strncasecmp _strnicmp

enum ExifTagTypes {
	CR3_EXIFTAG_TYPE_UNKNOWN = 0,
	CR3_EXIFTAG_TYPE_BYTE = 1,
	CR3_EXIFTAG_TYPE_ASCII = 2,
	CR3_EXIFTAG_TYPE_SHORT = 3,
	CR3_EXIFTAG_TYPE_LONG = 4,
	CR3_EXIFTAG_TYPE_RATIONAL = 5,
	CR3_EXIFTAG_TYPE_SBYTE = 6,
	CR3_EXIFTAG_TYPE_UNDEFINED = 7,
	CR3_EXIFTAG_TYPE_SSHORT = 8,
	CR3_EXIFTAG_TYPE_SLONG = 9,
	CR3_EXIFTAG_TYPE_SRATIONAL = 10,
	CR3_EXIFTAG_TYPE_FLOAT = 11,
	CR3_EXIFTAG_TYPE_DOUBLE = 12,
	CR3_EXIFTAG_TYPE_IFD = 13,
	CR3_EXIFTAG_TYPE_UNICODE = 14,
	CR3_EXIFTAG_TYPE_COMPLEX = 15,
	CR3_EXIFTAG_TYPE_LONG8 = 16,
	CR3_EXIFTAG_TYPE_SLONG8 = 17,
	CR3_EXIFTAG_TYPE_IFD8 = 18
};

typedef unsigned char uchar;
typedef unsigned short ushort;

#define CR3_CRXTRACKS_MAXCOUNT 16

#include "crx.h"
#include "comfun.h"

/* contents of tag CMP1 for relevant track in CR3 file */
struct crx_media_t
{
	unsigned MediaSize;
	unsigned MediaOffset;
	unsigned MediaType; /* 1 -> /C/RAW, 2-> JPEG */
};

struct unpacker_data_t
{
	short order;
	crx_data_header_t crx_header[CR3_CRXTRACKS_MAXCOUNT];
	struct crx_media_t crx_media[CR3_CRXTRACKS_MAXCOUNT];
	int crx_track_selected;
};

struct cr3_internal_data_t
{
	FILE* input;
	unsigned input_size;
	struct unpacker_data_t unpacker_data;
};

static const struct {
	ushort raw_width, raw_height, left_margin, top_margin, width_decrement, height_decrement;
} canon_sizes[] =
{
	/* raw_width, raw_height, left_margin, top_margin, width_decrement,
	 * height_decrement, mask01, mask03, mask11,
	 * mask13, CFA_filters.
	 */

	 /* 14 "EOS RP" */
	{ 4032, 2656, 112, 44, 10, 0 }, 

	/* 22 APS-C crop mode: "EOS R" */
	{ 4352, 2850, 144, 46, 0, 0 },  

	/* 38; "EOS-1D X MARK II" */
	{ 5568, 3708, 72, 38, 0, 0 },  

	/* 45 "EOS M100", "EOS 250D", "EOS M50" */
	{ 6288, 4056, 264, 36, 0, 0 }, 

	/* 48 "EOS R" */
	{ 6888, 4546, 146, 48, 0, 0 }, 

	/* 49 "EOS M6 II", "EOS 90D" */
	{ 7128, 4732, 144, 72, 0, 0 }, 

	/* Terminator */
	{ 0 }
};

static unsigned sgetn(int n, uchar* s)
{
	unsigned result = 0;
	while (n-- > 0)
		result = (result << 8) | (*s++);
	return result;
}

static void cleargps(gps_info_t* q)
{
	int i;

	for (i = 0; i < 3; i++)
		q->latitude[i][0] = q->latitude[i][1] = q->longitude[i][0] = q->longitude[i][1] = 0;
	q->altitude[0] = q->altitude[1] = 0;
	q->altref = q->latref = q->longref = q->gpsstatus = q->gpsfound = 0;
}

static void cr3_clear(cr3_t* data)
{
	cmpack_free(data->imgdata.image);
	data->imgdata.image = NULL;
	cmpack_free(data->imgdata.raw_alloc);
	data->imgdata.raw_alloc = NULL;

	data->imgdata.raw_height = data->imgdata.raw_width = data->imgdata.height = data->imgdata.width = 0;
	data->imgdata.top_margin = data->imgdata.left_margin = 0;
	data->imgdata.iwidth = data->imgdata.iheight = 0;

	memset(data->imgdata.make, 0, MAKE_SIZE);
	memset(data->imgdata.model, 0, MODEL_SIZE);
	data->imgdata.colors = 0;
	data->imgdata.filters = UINT_MAX; /* unknown */

	data->imgdata.shutter[0] = data->imgdata.shutter[1] = 0;
	memset(data->imgdata.timestamp, 0, TIMESTAMP_SIZE);

	data->imgdata.SensorWidth = data->imgdata.SensorHeight = 0;
	data->imgdata.SensorLeftBorder = data->imgdata.SensorTopBorder = -1;
	data->imgdata.SensorRightBorder = data->imgdata.SensorBottomBorder = 0;

	cleargps(&data->imgdata.parsed_gps);

	cmpack_free(data->intdata);
	data->intdata = NULL;
}

static ushort sget2(cr3_t* data, uchar* s)
{
	if (data->intdata->unpacker_data.order == 0x4949) /* "II" means little-endian */
		return s[0] | s[1] << 8;
	else /* "MM" means big-endian */
		return s[0] << 8 | s[1];
}

static ushort get2(cr3_t* data)
{
	uchar str[2] = { 0xff, 0xff };
	fread(str, 1, 2, data->intdata->input);
	return sget2(data, str);
}

static unsigned sget4(cr3_t* data, uchar* s)
{
	if (data->intdata->unpacker_data.order == 0x4949)
		return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
	else
		return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
}

static unsigned get4(cr3_t* data)
{
	uchar str[4] = { 0xff, 0xff, 0xff, 0xff };
	fread(str, 1, 4, data->intdata->input);
	return sget4(data, str);
}

static void tiff_get(cr3_t* data, unsigned base, unsigned* tag, unsigned* type,
	unsigned* len, unsigned* save)
{
	static const int _tagtype_dataunit_bytes[19] = {
		1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4, 2, 8, 8, 8, 8
	};

	*tag = get2(data);
	*type = get2(data);
	*len = get4(data);
	*save = ftell(data->intdata->input) + 4;
	if (*len * _tagtype_dataunit_bytes[(*type <= CR3_EXIFTAG_TYPE_IFD8) ? *type : 0] > 4)
		fseek(data->intdata->input, get4(data) + base, SEEK_SET);
}

static void parse_makernote(cr3_t* data, int base, int CR3_CTMDtag)
{
	char another_buf[128];
	fseek(data->intdata->input, -12, SEEK_CUR);
	fread(another_buf, 1, 12, data->intdata->input);

	char buf[11];
	fread(buf, 1, 10, data->intdata->input);
	buf[10] = '\0';

	if (!strncmp(buf, "KDK", 3) || /* these aren't TIFF tables */
		!strncmp(buf, "VER", 3) ||
		!strncmp(buf, "IIII", 4) ||
		!strncmp(buf, "MMMM", 4))
		return;

	unsigned entries;
	short morder, sorder = data->intdata->unpacker_data.order;

	unsigned fsize = data->intdata->input_size;

	if (!strncmp(buf, "CMT3", 4))
	{
		data->intdata->unpacker_data.order = sget2(data, (uchar*)(buf + 4));
		fseek(data->intdata->input, 2L, SEEK_CUR);
	}
	else if (CR3_CTMDtag)
	{
		data->intdata->unpacker_data.order = sget2(data, (uchar*)buf);
		fseek(data->intdata->input, -2L, SEEK_CUR);
	}
	else
	{
		fseek(data->intdata->input, -10, SEEK_CUR);
	}

	entries = get2(data);
	if (entries > 1000)
		return;

	morder = data->intdata->unpacker_data.order;
	while (entries--)
	{
		unsigned tag, type, len, save;
		data->intdata->unpacker_data.order = morder;
		tiff_get(data, base, &tag, &type, &len, &save);

		long _pos = ftell(data->intdata->input);
		if (len > 100 * 1024 * 1024) {
			/* 100Mb tag? No! */
			fseek(data->intdata->input, save, SEEK_SET);
			continue;
		}
		if (len > 8 && _pos + len > 2 * fsize)
		{
			fseek(data->intdata->input, save, SEEK_SET); /* Recover tiff-read position!! */
			continue;
		}

		if (tag == 0x00e0)
		{
			/* SensorInfo */
			data->imgdata.SensorWidth = (get2(data), get2(data));
			data->imgdata.SensorHeight = get2(data);
			data->imgdata.SensorLeftBorder = (get2(data), get2(data), get2(data));
			data->imgdata.SensorTopBorder = get2(data);
			data->imgdata.SensorRightBorder = get2(data);
			data->imgdata.SensorBottomBorder = get2(data);
		}

		fseek(data->intdata->input, save, SEEK_SET);
	}

	data->intdata->unpacker_data.order = sorder;
}

static void parse_tiff_ifd(cr3_t* data, int base)
{
	unsigned entries = get2(data);
	if (entries > 512)
		return;

	while (entries--)
	{
		unsigned tag, type, len, save;
		tiff_get(data, base, &tag, &type, &len, &save);
		unsigned savepos = ftell(data->intdata->input);
		if (len > 8 && savepos + len > 2 * data->intdata->input_size)
		{
			fseek(data->intdata->input, save, SEEK_SET); /* Recover tiff-read position!! */
			continue;
		}

		switch (tag)
		{            
		case 0x010f: /* 271, Make */
			fgets(data->imgdata.make, 64, data->intdata->input);
			break;
		case 0x0110: /* 272, Model */
			fgets(data->imgdata.model, 64, data->intdata->input);
			break;
		case 0x0132: /* 306, DateTime */
			fread(data->imgdata.timestamp, 19, 1, data->intdata->input);
			data->imgdata.timestamp[19] = 0;
			break;
		}
		fseek(data->intdata->input, save, SEEK_SET);
	}
}

static void parse_exif(cr3_t* data, int base)
{
	unsigned entries, tag, type, len, save;

	unsigned fsize = data->intdata->input_size;

	entries = get2(data);
	while (entries--)
	{
		tiff_get(data, base, &tag, &type, &len, &save);

		unsigned savepos = ftell(data->intdata->input);
		if (len > 8 && savepos + len > fsize * 2)
		{
			fseek(data->intdata->input, save, SEEK_SET); /* Recover tiff-read position!! */
			continue;
		}

		switch (tag)
		{
		case 0x829a: /* 33434 */
			data->imgdata.shutter[0] = get4(data);
			data->imgdata.shutter[1] = get4(data);
			break;
		}
		fseek(data->intdata->input, save, SEEK_SET);
	}
}

static void parse_gps(cr3_t* data, int base)
{
	int entries = get2(data);
	if (entries <= 40) {
		unsigned fsize = data->intdata->input_size;
		while (entries--)
		{
			unsigned tag, type, len, save, c, d;
			tiff_get(data, base, &tag, &type, &len, &save);
			if (len > 1024)
			{
				fseek(data->intdata->input, save, SEEK_SET); /* Recover tiff-read position!! */
				continue;                   /* no GPS tags are 1k or larger */
			}
			unsigned savepos = ftell(data->intdata->input);
			if (len > 8 && savepos + len > fsize * 2)
			{
				fseek(data->intdata->input, save, SEEK_SET); /* Recover tiff-read position!! */
				continue;
			}

			switch (tag)
			{
			case 0x0000:
				if (len == 4 && type == CR3_EXIFTAG_TYPE_BYTE) {
					for (c = 0; c < 4; c++)
						data->imgdata.parsed_gps.versionID[c] = getc(data->intdata->input);
					data->imgdata.parsed_gps.gpsfound = 1;
				}
				break;
			case 0x0001:
				data->imgdata.parsed_gps.latref = getc(data->intdata->input);
				break;
			case 0x0002:
				if (len <= 3 && type == CR3_EXIFTAG_TYPE_RATIONAL) {
					for (c = 0; c < len; c++)
						for (d = 0; d < 2; d++)
							data->imgdata.parsed_gps.latitude[c][d] = get4(data);
				}
				break;
			case 0x0003:
				data->imgdata.parsed_gps.longref = getc(data->intdata->input);
				break;
			case 0x0004:
				if (len <= 3 && type == CR3_EXIFTAG_TYPE_RATIONAL) {
					for (c = 0; c < len; c++)
						for (d = 0; d < 2; d++)
							data->imgdata.parsed_gps.longitude[c][d] = get4(data);
				}
				break;
			case 0x0005:
				data->imgdata.parsed_gps.altref = getc(data->intdata->input);
				break;
			case 0x0006:
				if (len == 1 && type == CR3_EXIFTAG_TYPE_RATIONAL) {
					for (d = 0; d < 2; d++)
						data->imgdata.parsed_gps.altitude[d] = get4(data);
				}
				break;
			case 0x0009:
				data->imgdata.parsed_gps.gpsstatus = getc(data->intdata->input);
				break;
			}
			fseek(data->intdata->input, save, SEEK_SET);
		}
	}
}

static int parseCR3(cr3_t* data, unsigned oAtomList, unsigned szAtomList, short* nesting, char* AtomNameStack, short* nTrack, short* TrackType)
{
	/*
	Atom starts with 4 bytes for Atom size and 4 bytes containing Atom name
	Atom size includes the length of the header and the size of all "contained"
	Atoms if Atom size == 1, Atom has the extended size stored in 8 bytes located
	after the Atom name if Atom size == 0, it is the last top-level Atom extending
	to the end of the file Atom name is often a 4 symbol mnemonic, but can be a
	4-byte integer
	*/
	static const char UIID_Canon[17] = "\x85\xc0\xb6\x87\x82\x0f\x11\xe0\x81\x11\xf4\xce\x46\x2b\x6a\x48";

	/*
	AtomType = 0 - unknown: "unk."
	AtomType = 1 - container atom: "cont"
	AtomType = 2 - leaf atom: "leaf"
	AtomType = 3 - can be container, can be leaf: "both"
	*/
	short AtomType;
	static const struct
	{
		char *AtomName;
		short AtomType;
	} AtomNamesList[] = {
		{"dinf", 1},
		{"edts", 1},
		{"fiin", 1},
		{"ipro", 1},
		{"iprp", 1},
		{"mdia", 1},
		{"meco", 1},
		{"mere", 1},
		{"mfra", 1},
		{"minf", 1},
		{"moof", 1},
		{"moov", 1},
		{"mvex", 1},
		{"paen", 1},
		{"schi", 1},
		{"sinf", 1},
		{"skip", 1},
		{"stbl", 1},
		{"stsd", 1},
		{"strk", 1},
		{"tapt", 1},
		{"traf", 1},
		{"trak", 1},

		{"cdsc", 2},
		{"colr", 2},
		{"dimg", 2},
		/* {"dref", 2}, */
		{"free", 2},
		{"frma", 2},
		{"ftyp", 2},
		{"hdlr", 2},
		{"hvcC", 2},
		{"iinf", 2},
		{"iloc", 2},
		{"infe", 2},
		{"ipco", 2},
		{"ipma", 2},
		{"iref", 2},
		{"irot", 2},
		{"ispe", 2},
		{"meta", 2},
		{"mvhd", 2},
		{"pitm", 2},
		{"pixi", 2},
		{"schm", 2},
		{"thmb", 2},
		{"tkhd", 2},
		{"url ", 2},
		{"urn ", 2},

		{"CCTP", 1},
		{"CRAW", 1},

		{"JPEG", 2},
		{"CDI1", 2},
		{"CMP1", 2},

		{"CNCV", 2},
		{"CCDT", 2},
		{"CTBO", 2},
		{"CMT1", 2},
		{"CMT2", 2},
		{"CMT3", 2},
		{"CMT4", 2},
		{"THMB", 2},
		{"co64", 2},
		{"mdat", 2},
		{"mdhd", 2},
		{"nmhd", 2},
		{"stsc", 2},
		{"stsz", 2},
		{"stts", 2},
		{"vmhd", 2},

		{"dref", 3},
		{"uuid", 3},

		{NULL}
	};

	const char *sHandlerType[] = { "unk.", "soun", "vide", "hint", "meta", NULL };

	int c, err = 0;

	ushort tL;                        /* Atom length represented in 4 or 8 bytes */
	char nmAtom[5];                   /* Atom name */
	unsigned oAtom, szAtom;           /* Atom offset and Atom size*/
	unsigned oAtomContent, szAtomContent; /* offset and size of Atom content */
	unsigned lHdr;

	char UIID[16];
	uchar CMP1[36];
	uchar CDI1[60];
	char HandlerType[5], MediaFormatID[5];
	unsigned relpos_inDir, relpos_inBox;
	unsigned szItem, Tag, lTag;
	ushort tItem;

	nmAtom[0] = MediaFormatID[0] = nmAtom[4] = MediaFormatID[4] = '\0';
	strcpy(HandlerType, sHandlerType[0]);
	oAtom = oAtomList;
	(*nesting)++;
	if (*nesting > 31)
		return -14; /* too deep nesting */
	short s_order = data->intdata->unpacker_data.order;

	while ((oAtom + 8) <= (oAtomList + szAtomList))
	{
		lHdr = 0;
		err = 0;
		data->intdata->unpacker_data.order = 0x4d4d;
		fseek(data->intdata->input, oAtom, SEEK_SET);
		szAtom = get4(data);
		for (c = 0; c < 4; c++)
			nmAtom[c] = AtomNameStack[(*nesting) * 4 + c] = fgetc(data->intdata->input);
		AtomNameStack[((*nesting) + 1) * 4] = '\0';
		tL = 4;
		AtomType = 0;

		for (c = 0; AtomNamesList[c].AtomName != NULL; c++) {
			if (!strcmp(nmAtom, AtomNamesList[c].AtomName))
			{
				AtomType = AtomNamesList[c].AtomType;
				break;
			}
		}

		if (!AtomType)
			err = 1;

		if (szAtom == 0)
		{
			if (*nesting != 0)
			{
				err = -2;
				goto fin;
			}
			szAtom = szAtomList - oAtom;
			oAtomContent = oAtom + 8;
			szAtomContent = szAtom - 8;
		}
		else if (szAtom == 1)
		{
			if ((oAtom + 16) > (oAtomList + szAtomList))
			{
				err = -3;
				goto fin;
			}
			tL = 8;
			if (get4(data) != 0) {
				err = -3;
				goto fin;
			}
			szAtom = get4(data);
			oAtomContent = oAtom + 16;
			szAtomContent = szAtom - 16;
		}
		else
		{
			oAtomContent = oAtom + 8;
			szAtomContent = szAtom - 8;
		}

		if (!strcmp(AtomNameStack, "uuid")) /* Top level uuid */
		{
			lHdr = 16;
		}

		if (!strcmp(nmAtom, "trak"))
		{
			(*nTrack)++;
			*TrackType = 0;
			if (*nTrack >= CR3_CRXTRACKS_MAXCOUNT)
				break;
		}
		if (!strcmp(AtomNameStack, "moovuuid"))
		{
			lHdr = 16;
			fread(UIID, 1, lHdr, data->intdata->input);
			if (!strncmp(UIID, UIID_Canon, lHdr))
			{
				AtomType = 1;
			}
			else
				fseek(data->intdata->input, -(long)lHdr, SEEK_CUR);
		}
		else if (!strcmp(AtomNameStack, "moovuuidCCTP"))
		{
			lHdr = 12;
		}
		else if (!strcmp(AtomNameStack, "moovuuidCMT1"))
		{
			short q_order = data->intdata->unpacker_data.order;
			short order = get2(data);
			data->intdata->unpacker_data.order = order;
			if ((tL != 4) || ((order != 0x4d4d) && (order != 0x4949)) || (get2(data) != 0x002a) || (get4(data) != 0x00000008))
			{
				err = -4;
				goto fin;
			}
			parse_tiff_ifd(data, (int)(oAtomContent));
			data->intdata->unpacker_data.order = q_order;
		}
		else if (!strcmp(AtomNameStack, "moovuuidCMT2"))
		{
			short q_order = data->intdata->unpacker_data.order;
			short order = get2(data);
			data->intdata->unpacker_data.order = order;
			if ((tL != 4) || ((order != 0x4d4d) && (order != 0x4949)) || (get2(data) != 0x002a) || (get4(data) != 0x00000008))
			{
				err = -5;
				goto fin;
			}
			parse_exif(data, (int)(oAtomContent));
			data->intdata->unpacker_data.order = q_order;
		}
		else if (!strcmp(AtomNameStack, "moovuuidCMT3"))
		{
			short q_order = data->intdata->unpacker_data.order;
			short order = get2(data);
			data->intdata->unpacker_data.order = order;
			if ((tL != 4) || ((order != 0x4d4d) && (order != 0x4949)) || (get2(data) != 0x002a) || (get4(data) != 0x00000008))
			{
				err = -6;
				goto fin;
			}
			fseek(data->intdata->input, -12L, SEEK_CUR);
			parse_makernote(data, (int)(oAtomContent), 0);
			data->intdata->unpacker_data.order = q_order;
		}
		else if (!strcmp(AtomNameStack, "moovuuidCMT4"))
		{
			short q_order = data->intdata->unpacker_data.order;
			short order = get2(data);
			data->intdata->unpacker_data.order = order;
			if ((tL != 4) || ((order != 0x4d4d) && (order != 0x4949)) || (get2(data) != 0x002a) || (get4(data) != 0x00000008))
			{
				err = -6;
				goto fin;
			}
			parse_gps(data, (int)(oAtomContent));
			data->intdata->unpacker_data.order = q_order;
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiahdlr"))
		{
			fseek(data->intdata->input, 8L, SEEK_CUR);
			for (c = 0; c < 4; c++)
				HandlerType[c] = fgetc(data->intdata->input);
			for (c = 1; sHandlerType[c] != NULL; c++) {
				if (!strcmp(HandlerType, sHandlerType[c])) {
					*TrackType = c;
					break;
				}
			}
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsd"))
		{
			if (szAtomContent >= 16)
			{
				fseek(data->intdata->input, 12L, SEEK_CUR);
				lHdr = 8;
			}
			else
			{
				err = -7;
				goto fin;
			}
			for (c = 0; c < 4; c++)
				MediaFormatID[c] = fgetc(data->intdata->input);
			if ((*TrackType == 2) && (!strcmp(MediaFormatID, "CRAW")))
			{
				if (szAtomContent >= 44)
					fseek(data->intdata->input, 24L, SEEK_CUR);
				else
				{
					err = -8;
					goto fin;
				}
			}
			else
			{
				AtomType = 2; /* only continue for CRAW */
				lHdr = 0;
			}

			/*ImageWidth =*/ get2(data);
			/*ImageHeight =*/ get2(data);
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsdCRAW"))
		{
			lHdr = 82;
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsdCRAWCMP1"))
		{
			if (szAtomContent < 40) {
				err = -7;
				goto fin;
			}
			fread(CMP1, 1, 36, data->intdata->input);
			if (*nTrack >= 0 && *nTrack < CR3_CRXTRACKS_MAXCOUNT) {
				if (!crxParseImageHeader(data->intdata->unpacker_data.crx_header + (*nTrack), CMP1))
					data->intdata->unpacker_data.crx_media[*nTrack].MediaType = 1;
			}
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsdCRAWCDI1")) {
			if (szAtomContent >= 60) {
				fread(CDI1, 1, 60, data->intdata->input);
				if (!strncmp((char*)CDI1 + 8, "IAD1", 4) && (sgetn(8, CDI1) == 0x38)) {
					/* sensor area at CDI1+12, 4 16-bit values
					 * Bayer pattern? - next 4 16-bit values
					 * image area, next 4 16-bit values
					 */ 
					for (c = 0; c < 4; c++)
						sgetn(2, CDI1 + 12 + 3 * 4 * 2 + 2 * c);
					for (c = 0; c < 4; c++)
						sgetn(2, CDI1 + 12 + 4 * 4 * 2 + 2 * c);
					for (c = 0; c < 4; c++)
						sgetn(2, CDI1 + 12 + 5 * 4 * 2 + 2 * c);
				}
			}
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsdCRAWJPEG"))
		{
			data->intdata->unpacker_data.crx_media[*nTrack].MediaType = 2;
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblstsz"))
		{
			if (szAtomContent == 12)
				fseek(data->intdata->input, 4L, SEEK_CUR);
			else if (szAtomContent == 16)
				fseek(data->intdata->input, 12L, SEEK_CUR);
			else
			{
				err = -9;
				goto fin;
			}
			data->intdata->unpacker_data.crx_media[*nTrack].MediaSize = get4(data);
		}
		else if (!strcmp(AtomNameStack, "moovtrakmdiaminfstblco64"))
		{
			if (szAtomContent == 16)
				fseek(data->intdata->input, 8L, SEEK_CUR);
			else
			{
				err = -10;
				goto fin;
			}
			if (get4(data) != 0) {
				err = -10;
				goto fin;
			}
			data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset = get4(data);
		}

		if (*nTrack >= 0 && *nTrack < CR3_CRXTRACKS_MAXCOUNT &&
			data->intdata->unpacker_data.crx_media[*nTrack].MediaSize && data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset &&
			((oAtom + szAtom) >= (oAtomList + szAtomList)) &&
			!strncmp(AtomNameStack, "moovtrakmdiaminfstbl", 20))
		{
			if ((*TrackType == 4) && (!strcmp(MediaFormatID, "CTMD")))
			{
				data->intdata->unpacker_data.order = 0x4949;
				relpos_inDir = 0;
				while (relpos_inDir + 6 < data->intdata->unpacker_data.crx_media[*nTrack].MediaSize)
				{
					if (data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inDir > data->intdata->input_size - 6) /* need at least 6 bytes */
					{
						err = -11;
						goto fin;
					}
					fseek(data->intdata->input, data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inDir, SEEK_SET);
					szItem = get4(data);
					tItem = get2(data);
					if (szItem < 1 || ((relpos_inDir + szItem) > data->intdata->unpacker_data.crx_media[*nTrack].MediaSize))
					{
						err = -11;
						goto fin;
					}
					if ((tItem == 7) || (tItem == 8) || (tItem == 9))
					{
						relpos_inBox = relpos_inDir + 12;
						while (relpos_inBox + 8 < relpos_inDir + szItem)
						{
							if (data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inBox > data->intdata->input_size - 8) /* need at least 8 bytes */
							{
								err = -11;
								goto fin;
							}
							fseek(data->intdata->input, data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inBox, SEEK_SET);
							lTag = get4(data);
							Tag = get4(data);
							if (lTag < 8)
							{
								err = -12;
								goto fin;
							}
							else if ((relpos_inBox + lTag) > (relpos_inDir + szItem))
							{
								err = -11;
								goto fin;
							}
							if ((Tag == 0x927c) && ((tItem == 7) || (tItem == 8)))
							{
								fseek(data->intdata->input, data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inBox + 8L, SEEK_SET);
								short q_order = data->intdata->unpacker_data.order;
								short order = get2(data);
								data->intdata->unpacker_data.order = order;
								if (((order != 0x4d4d) && (order != 0x4949)) || (get2(data) != 0x002a) || (get4(data) != 0x00000008))
								{
									err = -13;
									goto fin;
								}
								fseek(data->intdata->input, -8L, SEEK_CUR);
								parse_makernote(data, (int)(data->intdata->unpacker_data.crx_media[*nTrack].MediaOffset + relpos_inBox + 8), 1);
								data->intdata->unpacker_data.order = q_order;
							}
							relpos_inBox += lTag;
						}
					}
					relpos_inDir += szItem;
				}
				data->intdata->unpacker_data.order = 0x4d4d;
			}
		}
		if (AtomType == 1)
		{
			err = parseCR3(data, oAtomContent + lHdr, szAtomContent - lHdr, nesting, AtomNameStack, nTrack, TrackType);
			if (err)
				goto fin;
		}
		oAtom += szAtom;
	}

fin:
	(*nesting)--;
	if (*nesting >= 0)
		AtomNameStack[*nesting * 4] = '\0';
	data->intdata->unpacker_data.order = s_order;
	return err;
}

void cr3_init(cr3_t* data)
{
	memset(&data->imgdata, 0, sizeof(data->imgdata));
	cleargps(&data->imgdata.parsed_gps);
	data->intdata = 0;
}

void cr3_free(cr3_t* data)
{
	cr3_clear(data);
}

int cr3_open(cr3_t* data, FILE* stream)
{
	char head[64] = { 0 };
	int i;
	int err;
	short nesting, nTrack, TrackType;
	char AtomNameStack[128];

	if (!stream)
		return CR3_FILE_CLOSED;

	cr3_clear(data);

	if (!data->intdata)
		data->intdata = (cr3_internal_data_p)cmpack_malloc(sizeof(struct cr3_internal_data_t));
	memset(data->intdata, 0, sizeof(struct cr3_internal_data_t));
	data->intdata->input = stream;

	if (fseek(stream, 0, SEEK_END) != 0)
		return CR3_IO_ERROR;
	data->intdata->input_size = ftell(stream);
	if (fseek(stream, 0, SEEK_SET) != 0)
		return CR3_IO_ERROR;

	if (fread(head, 1, 64, data->intdata->input) != 64)
		return CR3_IO_ERROR;

	if (memcmp(head + 4, "ftypcrx ", 8))
		return CR3_FILE_UNSUPPORTED;

	data->imgdata.colors = 3;
	data->intdata->unpacker_data.order = 0;

	nesting = nTrack = -1;
	err = parseCR3(data, 0, data->intdata->input_size, &nesting, AtomNameStack, &nTrack, &TrackType);
	if (err != 0 && err != -14) {
		cr3_clear(data);
		return CR3_FILE_UNSUPPORTED;
	}

	int maxbitcount = 0, framei = -1, framecnt = 0;
	for (i = 0; i <= nTrack && i < CR3_CRXTRACKS_MAXCOUNT; i++) {
		if (data->intdata->unpacker_data.crx_media[i].MediaType == 1) {
			/* RAW */
			crx_data_header_t* d = &data->intdata->unpacker_data.crx_header[i];
			int bitcounts = d->nBits * d->f_width * d->f_height;
			if (bitcounts >= 8) {
				if (bitcounts == maxbitcount)
					framecnt++;
				if (bitcounts > maxbitcount) {
					maxbitcount = bitcounts;
					framecnt = 1;
					framei = i;
				}
				else if (bitcounts == maxbitcount)
					framecnt++;
			}
		}
	}
	if (framei < 0) {
		cr3_clear(data);
		return CR3_FILE_UNSUPPORTED;
	}

	crx_data_header_t* d = &data->intdata->unpacker_data.crx_header[framei];
	data->imgdata.raw_width = d->f_width;
	data->imgdata.raw_height = d->f_height;
	switch (d->cfaLayout)
	{
	case 0:
		data->imgdata.filters = 0x94949494;
		break;
	case 1:
		data->imgdata.filters = 0x61616161;
		break;
	case 2:
		data->imgdata.filters = 0x49494949;
		break;
	case 3:
		data->imgdata.filters = 0x16161616;
		break;
	default:
		data->imgdata.filters = 0x94949494;
		break;
	}
	data->imgdata.filters |= ((data->imgdata.filters >> 2 & 0x22222222) | (data->imgdata.filters << 2 & 0x88888888)) & data->imgdata.filters << 1;

	/* make sure strings are terminated */
	data->imgdata.make[63] = data->imgdata.model[63] = 0;

	if (!data->imgdata.height)
		data->imgdata.height = data->imgdata.raw_height;
	if (!data->imgdata.width)
		data->imgdata.width = data->imgdata.raw_width;

	int fromtable = 0;
	for (i = 0; i < canon_sizes[i].raw_width; i++)
		if (data->imgdata.raw_width == canon_sizes[i].raw_width && data->imgdata.raw_height == canon_sizes[i].raw_height)
		{
			data->imgdata.width = data->imgdata.raw_width - (data->imgdata.left_margin = canon_sizes[i].left_margin);
			data->imgdata.height = data->imgdata.raw_height - (data->imgdata.top_margin = canon_sizes[i].top_margin);
			data->imgdata.width -= canon_sizes[i].width_decrement;
			data->imgdata.height -= canon_sizes[i].height_decrement;
			fromtable = 1;
		}
	if (!fromtable) /* not known, but metadata known */
	{
		if (data->imgdata.SensorWidth == data->imgdata.raw_width && data->imgdata.SensorHeight == data->imgdata.raw_height)
		{
			data->imgdata.left_margin = (data->imgdata.SensorLeftBorder + 1) & 0xfffe; /* round to 2 */
			data->imgdata.width = data->imgdata.SensorRightBorder - data->imgdata.left_margin;
			data->imgdata.top_margin = (data->imgdata.SensorTopBorder + 1) & 0xfffe;
			data->imgdata.height = data->imgdata.SensorBottomBorder - data->imgdata.top_margin;
		}
	}

	/* Early reject for damaged images */
	if (data->imgdata.height < 22 || data->imgdata.width < 22 || (data->imgdata.width + data->imgdata.left_margin > 65535) || (data->imgdata.height + data->imgdata.top_margin > 65535))
	{
		cr3_clear(data);
		return CR3_FILE_UNSUPPORTED;
	}

	if (data->imgdata.raw_height < data->imgdata.height)
		data->imgdata.raw_height = data->imgdata.height;
	if (data->imgdata.raw_width < data->imgdata.width)
		data->imgdata.raw_width = data->imgdata.width;

	if (data->imgdata.raw_width < 22 || data->imgdata.raw_width > 64000 || data->imgdata.raw_height < 22 || data->imgdata.raw_height > 64000 ||
		data->imgdata.raw_width <= data->imgdata.left_margin || data->imgdata.raw_height <= data->imgdata.top_margin) {
		cr3_clear(data);
		return CR3_FILE_UNSUPPORTED;
	}

	if ((data->imgdata.top_margin % 2) || (data->imgdata.left_margin % 2))
	{
		int crop[2] = { 0,0 };
		unsigned filt = 0;
		int c;
		if (data->imgdata.top_margin % 2)
		{
			data->imgdata.top_margin += 1;
			data->imgdata.height -= 1;
			crop[1] = 1;
		}
		if (data->imgdata.left_margin % 2)
		{
			data->imgdata.left_margin += 1;
			data->imgdata.width -= 1;
			crop[0] = 1;
		}
		for (c = 0; c < 16; c++) {
			int row = (c >> 1) + (crop[1]), col = (c & 1) + (crop[0]);
			filt |= (data->imgdata.filters >> (((row << 1 & 14) | (col & 1)) << 1) & 3) << c * 2;
		}
		data->imgdata.filters = filt;
	}

	if (data->imgdata.filters >= 1000) {
		data->imgdata.width &= 65534;
		data->imgdata.height &= 65534;
	}

	data->intdata->unpacker_data.crx_track_selected = framei;

	return CR3_SUCCESS;
}

int cr3_unpack(cr3_t* data)
{
	if (!data->intdata->input)
		return CR3_IO_ERROR;

	if (!data->imgdata.filters || !data->imgdata.colors)
		return CR3_FILE_UNSUPPORTED;

	int framei = data->intdata->unpacker_data.crx_track_selected;
	if (framei < 0 || framei >= CR3_CRXTRACKS_MAXCOUNT)
		return CR3_IO_ERROR;

	crx_data_header_t* hdr = data->intdata->unpacker_data.crx_header + framei;
	unsigned data_offset = data->intdata->unpacker_data.crx_media[framei].MediaOffset;
	unsigned data_size = data->intdata->unpacker_data.crx_media[framei].MediaSize;
	if (data_size < hdr->mdatHdrSize) 
		return CR3_IO_ERROR;

	int rwidth = data->imgdata.raw_width, rheight = data->imgdata.raw_height;
	if (rwidth < data->imgdata.width + data->imgdata.left_margin)
		rwidth = data->imgdata.width + data->imgdata.left_margin;
	if (rheight < data->imgdata.height + data->imgdata.top_margin)
		rheight = data->imgdata.height + data->imgdata.top_margin;
	if (rwidth > 65535 || rheight > 65535) { 
		/* No way to make image larger than 64k pix */
		return CR3_IO_ERROR;
	}
	if (rwidth * (rheight + 8) > (INT_MAX / 2)) 
		return CR3_TOO_BIG;

	cmpack_free(data->imgdata.raw_alloc);
	data->imgdata.raw_alloc = (ushort*)cmpack_malloc(rwidth * (rheight + 8) * sizeof(short));

	if (fseek(data->intdata->input, data_offset, SEEK_SET) != 0 ||
		crxLoadRaw(hdr, data->intdata->input, data->intdata->input_size, data_offset, data_size, (short*)data->imgdata.raw_alloc) != 0) 
		return CR3_IO_ERROR;

	return 0;
}

int cr3_process(cr3_t* data)
{
	int row, col;

	data->imgdata.iheight = (data->imgdata.height + 1) >> 1;
	data->imgdata.iwidth = (data->imgdata.width + 1) >> 1;

	int alloc_width = data->imgdata.iwidth;
	int alloc_height = data->imgdata.iheight;
	int alloc_sz = alloc_width * alloc_height;
	int raw_row_stride = data->imgdata.raw_width;

	cmpack_free(data->imgdata.image);
	data->imgdata.image = (ushort(*)[4])cmpack_calloc(alloc_sz, 4 * sizeof(short));
	if (!data->imgdata.image)
		return -1;

	/* Move saved bitmap to imgdata.image */
	if ((!data->imgdata.filters && data->imgdata.colors != 1) || !data->imgdata.raw_alloc)
		return -1;

	int maxHeight = data->imgdata.height;
	if (maxHeight > data->imgdata.raw_height - data->imgdata.top_margin)
		maxHeight = data->imgdata.raw_height - data->imgdata.top_margin;
	int maxWidth = data->imgdata.width;
	if (maxWidth > data->imgdata.raw_width - data->imgdata.left_margin)
		maxWidth = data->imgdata.raw_width - data->imgdata.left_margin;
	for (row = 0; row < maxHeight; row++) {
		for (col = 0; col < maxWidth; col++) {
			unsigned short val = data->imgdata.raw_alloc[(row + data->imgdata.top_margin) * raw_row_stride + (col + data->imgdata.left_margin)];
			int cc = (data->imgdata.filters >> (((row << 1 & 14) | (col & 1)) << 1) & 3);
			data->imgdata.image[((row) >> 1) * data->imgdata.iwidth + ((col) >> 1)][cc] = val;
		}
	}
	return 0;
}
