// license:BSD-3-Clause
// copyright-holders:Raphael Nabet
/****************************************************************************

    macbin.c

    MacBinary filter for use with Mac and ProDOS drivers

*****************************************************************************

  MacBinary file format

  Offset  Length  Description
  ------  ------  -----------
       0       1  [I]   Magic byte (0x00)
       1      64  [I]   File name (Pascal String)
      65       4  [I]   File Type Code
      69       4  [I]   File Creator Code
      73       1  [I]   Finder Flags (bits 15-8)
      74       1  [I]   Magic byte (0x00)
      75       2  [I]   File Vertical Position
      77       2  [I]   File Horizontal Position
      79       2  [I]   Window/Folder ID
      81       1  [I]   Protected (bit 0)
      82       1  [I]   Magic byte (0x00)
      83       4  [I]   Data Fork Length
      87       4  [I]   Resource Fork Length
      91       4  [I]   Creation Date
      95       4  [I]   Last Modified Date
      99       2  [I]   "Get Info" comment length
     101       1  [II]  Finder Flags (bits 7-0)
     102       4  [III] MacBinary III Signature 'mBIN'
     106       1  [III] Script of Filename
     107       1  [III] Extended Finder Flags
     116       4  [II]  Unpacked Length
     120       2  [II]  Secondary Header Length
     122       1  [II]  MacBinary II Version Number (II: 0x81, III: 0x82)
     123       1  [II]  Minimum Compatible MacBinary II Version Number (0x81)
     124       2  [II]  CRC of previous 124 bytes

    For more information, consult http://www.lazerware.com/formats/macbinary.html

    TODO: I believe that the script code is some sort of identifier identifying
    the character set used for the filename.  If this is true, we are not
    handling that properly

*****************************************************************************/

#include <string.h>

#include "imgtool.h"
#include "formats/imageutl.h"
#include "macutil.h"



static uint32_t pad128(uint32_t length)
{
	if (length % 128)
		length += 128 - (length % 128);
	return length;
}



static imgtoolerr_t macbinary_readfile(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &destf)
{
	static const uint32_t attrs[] =
	{
		IMGTOOLATTR_TIME_CREATED,
		IMGTOOLATTR_TIME_LASTMODIFIED,
		IMGTOOLATTR_INT_MAC_TYPE,
		IMGTOOLATTR_INT_MAC_CREATOR,
		IMGTOOLATTR_INT_MAC_FINDERFLAGS,
		IMGTOOLATTR_INT_MAC_COORDX,
		IMGTOOLATTR_INT_MAC_COORDY,
		IMGTOOLATTR_INT_MAC_FINDERFOLDER,
		IMGTOOLATTR_INT_MAC_SCRIPTCODE,
		IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS,
		0
	};
	imgtoolerr_t err;
	uint8_t header[128];
	const char *basename;
	int i;

	uint32_t type_code = 0x3F3F3F3F;
	uint32_t creator_code = 0x3F3F3F3F;
	uint16_t finder_flags = 0;
	uint16_t coord_x = 0;
	uint16_t coord_y = 0;
	uint16_t finder_folder = 0;
	uint8_t script_code = 0;
	uint8_t extended_flags = 0;

	imgtool_forkent fork_entries[4];
	const imgtool_forkent *data_fork = NULL;
	const imgtool_forkent *resource_fork = NULL;
	uint32_t creation_time = 0;
	uint32_t lastmodified_time = 0;
	imgtool_attribute attr_values[10];

	/* get the forks */
	err = partition.list_file_forks(filename, fork_entries, sizeof(fork_entries));
	if (err)
		return err;
	for (i = 0; fork_entries[i].type != FORK_END; i++)
	{
		if (fork_entries[i].type == FORK_DATA)
			data_fork = &fork_entries[i];
		else if (fork_entries[i].type == FORK_RESOURCE)
			resource_fork = &fork_entries[i];
	}

	/* get the attributes */
	err = partition.get_file_attributes(filename, attrs, attr_values);
	if (err && (ERRORCODE(err) != IMGTOOLERR_UNIMPLEMENTED))
		return err;
	if (err == IMGTOOLERR_SUCCESS)
	{
		creation_time     = mac_setup_time(attr_values[0].t);
		lastmodified_time = mac_setup_time(attr_values[1].t);
		type_code         = attr_values[2].i;
		creator_code      = attr_values[3].i;
		finder_flags      = attr_values[4].i;
		coord_x           = attr_values[5].i;
		coord_y           = attr_values[6].i;
		finder_folder     = attr_values[7].i;
		script_code       = attr_values[8].i;
		extended_flags    = attr_values[9].i;
	}

	memset(header, 0, sizeof(header));

	/* place filename */
	basename = filename;
	while(basename[strlen(basename) + 1])
		basename += strlen(basename) + 1;
	pascal_from_c_string((unsigned char *) &header[1], 64, basename);

	place_integer_be(header,  65, 4, type_code);
	place_integer_be(header,  69, 4, creator_code);
	place_integer_be(header,  73, 1, (finder_flags >> 8) & 0xFF);
	place_integer_be(header,  75, 2, coord_x);
	place_integer_be(header,  77, 2, coord_y);
	place_integer_be(header,  79, 2, finder_folder);
	place_integer_be(header,  83, 4, data_fork ? data_fork->size : 0);
	place_integer_be(header,  87, 4, resource_fork ? resource_fork->size : 0);
	place_integer_be(header,  91, 4, creation_time);
	place_integer_be(header,  95, 4, lastmodified_time);
	place_integer_be(header, 101, 1, (finder_flags >> 0) & 0xFF);
	place_integer_be(header, 102, 4, 0x6D42494E);
	place_integer_be(header, 106, 1, script_code);
	place_integer_be(header, 107, 1, extended_flags);
	place_integer_be(header, 122, 1, 0x82);
	place_integer_be(header, 123, 1, 0x81);
	place_integer_be(header, 124, 2, ccitt_crc16(0, header, 124));

	destf.write(header, sizeof(header));

	if (data_fork)
	{
		err = partition.read_file(filename, "", destf, NULL);
		if (err)
			return err;

		destf.fill(0, pad128(data_fork->size));
	}

	if (resource_fork)
	{
		err = partition.read_file(filename, "RESOURCE_FORK", destf, NULL);
		if (err)
			return err;

		destf.fill(0, pad128(resource_fork->size));
	}

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t write_fork(imgtool::partition &partition, const char *filename, const char *fork,
	imgtool::stream &sourcef, uint64_t pos, uint64_t fork_len, util::option_resolution *opts)
{
	imgtoolerr_t err;
	imgtool::stream::ptr mem_stream;
	size_t len;

	if (fork_len > 0)
	{
		mem_stream = imgtool::stream::open_mem(nullptr, 0);
		if (!mem_stream)
			return IMGTOOLERR_OUTOFMEMORY;

		sourcef.seek(pos, SEEK_SET);
		len = imgtool::stream::transfer(*mem_stream, sourcef, fork_len);
		if (len < fork_len)
			mem_stream->fill(0, fork_len);

		mem_stream->seek(0, SEEK_SET);
		err = partition.write_file(filename, fork, *mem_stream, opts, NULL);
		if (err)
			return err;
	}

	return IMGTOOLERR_SUCCESS;
}



static imgtoolerr_t macbinary_writefile(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *opts)
{
	static const uint32_t attrs[] =
	{
		IMGTOOLATTR_TIME_CREATED,
		IMGTOOLATTR_TIME_LASTMODIFIED,
		IMGTOOLATTR_INT_MAC_TYPE,
		IMGTOOLATTR_INT_MAC_CREATOR,
		IMGTOOLATTR_INT_MAC_FINDERFLAGS,
		IMGTOOLATTR_INT_MAC_COORDX,
		IMGTOOLATTR_INT_MAC_COORDY,
		IMGTOOLATTR_INT_MAC_FINDERFOLDER,
		IMGTOOLATTR_INT_MAC_SCRIPTCODE,
		IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS,
		0
	};
	imgtoolerr_t err;
	imgtool::image *image = &partition.image();
	uint8_t header[128];
	uint32_t datafork_size;
	uint32_t resourcefork_size;
	uint64_t total_size;
	uint32_t creation_time;
	uint32_t lastmodified_time;
	//int version;
	imgtool_attribute attr_values[10];

	uint32_t type_code;
	uint32_t creator_code;
	uint16_t finder_flags;
	uint16_t coord_x;
	uint16_t coord_y;
	uint16_t finder_folder;
	uint8_t script_code = 0;
	uint8_t extended_flags = 0;

	/* read in the header */
	memset(header, 0, sizeof(header));
	sourcef.read(header, sizeof(header));

	/* check magic and zero fill bytes */
	if (header[0] != 0x00)
		return IMGTOOLERR_CORRUPTFILE;
	if (header[74] != 0x00)
		return IMGTOOLERR_CORRUPTFILE;
	if (header[82] != 0x00)
		return IMGTOOLERR_CORRUPTFILE;

	datafork_size = pick_integer_be(header, 83, 4);
	resourcefork_size = pick_integer_be(header, 87, 4);
	total_size = sourcef.size();

	/* size of a MacBinary header is always 128 bytes */
	if (total_size - pad128(datafork_size) - pad128(resourcefork_size) != 128)
		return IMGTOOLERR_CORRUPTFILE;

	/* check filename length byte */
	if ((header[1] <= 0x00) || (header[1] > 0x3F))
		return IMGTOOLERR_CORRUPTFILE;

	/* check the CRC */
	if (pick_integer_be(header, 124, 2) != ccitt_crc16(0, header, 124))
	{
		/* the CRC does not match; this file is MacBinary I */
		//version = 1;
	}
	else if (pick_integer_be(header, 102, 4) != 0x6D42494E)
	{
		/* did not see 'mBIN'; this file is MacBinary II */
		if (header[122] < 0x81)
			return IMGTOOLERR_CORRUPTFILE;
		if (header[123] < 0x81)
			return IMGTOOLERR_CORRUPTFILE;
		//version = 2;
	}
	else
	{
		/* we did see 'mBIN'; this file is MacBinary III */
		if (header[122] < 0x82)
			return IMGTOOLERR_CORRUPTFILE;
		if (header[123] < 0x81)
			return IMGTOOLERR_CORRUPTFILE;
		//version = 3;
	}

	type_code         = pick_integer_be(header, 65, 4);
	creator_code      = pick_integer_be(header, 69, 4);
	finder_flags      = pick_integer_be(header, 73, 1) << 8;
	coord_x           = pick_integer_be(header, 75, 2);
	coord_y           = pick_integer_be(header, 77, 2);
	finder_folder     = pick_integer_be(header, 79, 2);
	creation_time     = pick_integer_be(header, 91, 4);
	lastmodified_time = pick_integer_be(header, 95, 4);

	if (image)
	{
		/* write out both forks */
		err = write_fork(partition, filename, "", sourcef, sizeof(header), datafork_size, opts);
		if (err)
			return err;
		err = write_fork(partition, filename, "RESOURCE_FORK", sourcef, sizeof(header) + pad128(datafork_size), resourcefork_size, opts);
		if (err)
			return err;

		/* set up attributes */
		attr_values[0].t = mac_crack_time(creation_time);
		attr_values[1].t = mac_crack_time(lastmodified_time);
		attr_values[2].i = type_code;
		attr_values[3].i = creator_code;
		attr_values[4].i = finder_flags;
		attr_values[5].i = coord_x;
		attr_values[6].i = coord_y;
		attr_values[7].i = finder_folder;
		attr_values[8].i = script_code;
		attr_values[9].i = extended_flags;

		err = partition.put_file_attributes(filename, attrs, attr_values);
		if (err)
			return err;
	}

	return IMGTOOLERR_SUCCESS;
}


// this was completely broken - it was calling macbinary_writefile() with a nullptr partition
#if 0
static imgtoolerr_t macbinary_checkstream(imgtool::stream &stream, imgtool_suggestion_viability_t *viability)
{
	imgtoolerr_t err;

	err = macbinary_writefile(NULL, NULL, NULL, stream, NULL);
	if (err == IMGTOOLERR_CORRUPTFILE)
	{
		/* the filter returned corrupt; this is not a valid file */
		*viability = SUGGESTION_END;
		err = IMGTOOLERR_SUCCESS;
	}
	else if (err == IMGTOOLERR_SUCCESS)
	{
		/* success; lets recommend this filter */
		*viability = SUGGESTION_RECOMMENDED;
	}
	return err;
}
#endif



void filter_macbinary_getinfo(uint32_t state, union filterinfo *info)
{
	switch(state)
	{
		case FILTINFO_STR_NAME:         info->s = "macbinary"; break;
		case FILTINFO_STR_HUMANNAME:    info->s = "MacBinary"; break;
		case FILTINFO_STR_EXTENSION:    info->s = "bin"; break;
		case FILTINFO_PTR_READFILE:     info->read_file = macbinary_readfile; break;
		case FILTINFO_PTR_WRITEFILE:    info->write_file = macbinary_writefile; break;
		//case FILTINFO_PTR_CHECKSTREAM:  info->check_stream = macbinary_checkstream; break;
	}
}
