/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Support for 2MG/2IMG wrapper files.
 *
 * This needs to be directly accessible from external applications, so try not
 * to hook into private DiskImg state.
 */
#include "StdAfx.h"
#include "TwoImg.h"
#include "DiskImgPriv.h"

///*static*/ const char* TwoImgHeader::kMagic = "2IMG";	// file magic #
///*static*/ const char* TwoImgHeader::kCreator = "CdrP";	// our "creator" ID


/*
 * Initialize a header to default values, using the supplied image length
 * where appropriate.
 *
 * Sets up a header for a 2MG file with the disk data and nothing else.
 *
 * Returns 0 on success, -1 if one of the arguments was bad.
 */
int
TwoImgHeader::InitHeader(int imageFormat, long imageSize, long imageBlockCount)
{
	if (imageSize <= 0)
		return -1;
	if (imageFormat < kImageFormatDOS || imageFormat > kImageFormatNibble)
		return -1;

    if (imageFormat != kImageFormatNibble &&
        imageSize != imageBlockCount * 512)
    {
        WMSG3("2MG InitHeader: bad sizes %d %ld %ld\n", imageFormat,
            imageSize, imageBlockCount);
        return -1;
    }

	assert(fComment == nil);

	//memcpy(fMagic, kMagic, 4);
	//memcpy(fCreator, kCreator, 4);
	fMagic = kMagic;
	fCreator = kCreatorCiderPress;
	fHeaderLen = kOurHeaderLen;
	fVersion = kOurVersion;
	fImageFormat = imageFormat;
	fFlags = 0;
	fNumBlocks = imageBlockCount;
	fDataOffset = kOurHeaderLen;
	fDataLen = imageSize;
	fCmtOffset = 0;
	fCmtLen = 0;
	fCreatorOffset = 0;
	fCreatorLen = 0;
	fSpare[0] = fSpare[1] = fSpare[2] = fSpare[3] = 0;

	return 0;
}

/*
 * Get the DOS volume number.
 *
 * If not set, we currently return the initial value (-1), rather than the
 * default volume number.  For the way we currently make use of this, this
 * makes the most sense.
 */
short
TwoImgHeader::GetDOSVolumeNum(void) const
{
	assert(fFlags & kDOSVolumeSet);
	return fDOSVolumeNum;
}

/*
 * Set the DOS volume number.
 */
void
TwoImgHeader::SetDOSVolumeNum(short dosVolumeNum)
{
    assert(dosVolumeNum >= 0 && dosVolumeNum < 256);
    fFlags |= dosVolumeNum;
    fFlags |= kDOSVolumeSet;
}

/*
 * Set the comment.
 */
void
TwoImgHeader::SetComment(const char* comment)
{
	delete[] fComment;
	if (comment == nil) {
		fComment = nil;
	} else {
		fComment = new char[strlen(comment)+1];
		if (fComment != nil)
			strcpy(fComment, comment);
		// else throw alloc failure
	}

	if (fComment == nil) {
		fCmtLen = 0;
		fCmtOffset = 0;
		if (fCreatorOffset > 0)
			fCreatorOffset = fDataOffset + fDataLen;
	} else {
		fCmtLen = strlen(fComment);
		fCmtOffset = fDataOffset + fDataLen;
		if (fCreatorOffset > 0)
			fCreatorOffset = fCmtOffset + fCmtLen;
	}
}

/*
 * Set the creator chunk.
 */
void
TwoImgHeader::SetCreatorChunk(const void* chunk, long len)
{
	assert(len >= 0);

	delete[] fCreatorChunk;
	if (chunk == nil || len == 0) {
		fCreatorChunk = nil;
	} else {
		fCreatorChunk = new char[len];
		if (fCreatorChunk != nil)
			memcpy(fCreatorChunk, chunk, len);
		// else throw alloc failure
	}

	if (fCreatorChunk == nil) {
		fCreatorLen = 0;
		fCreatorOffset = 0;
	} else {
		fCreatorLen = len;
		if (fCmtOffset > 0)
			fCreatorOffset = fCmtOffset + fCmtLen;
		else
			fCreatorOffset = fDataOffset + fDataLen;
	}
}

/*
 * Read the header from a 2IMG file.  Pass in "totalLength" as a sanity check.
 *
 * THOUGHT: provide a simple GenericFD conversion for FILE*, and then just
 * call the GenericFD version of ReadHeader.
 *
 * Returns 0 on success, nonzero on error or invalid header.
 */
int
TwoImgHeader::ReadHeader(FILE* fp, long totalLength)
{
	unsigned char buf[kOurHeaderLen];

	fread(buf, kOurHeaderLen, 1, fp);
	if (ferror(fp))
		return errno ? errno : -1;

	if (UnpackHeader(buf, totalLength) != 0)
		return -1;

	/*
	 * Extract the comment, if any.
	 */
	if (fCmtOffset > 0 && fCmtLen > 0) {
		if (GetChunk(fp, fCmtOffset - kOurHeaderLen, fCmtLen,
			(void**) &fComment) != 0)
		{
			WMSG0("Throwing comment away\n");
			fCmtLen = 0;
			fCmtOffset = 0;
		} else {
			WMSG1("Got comment: '%s'\n", fComment);
		}
	}

	/*
	 * Extract the creator chunk, if any.
	 */
	if (fCreatorOffset > 0 && fCreatorLen > 0) {
		if (GetChunk(fp, fCreatorOffset - kOurHeaderLen, fCreatorLen,
			(void**) &fCreatorChunk) != 0)
		{
			WMSG0("Throwing creator chunk away\n");
			fCreatorLen = 0;
			fCreatorOffset = 0;
		} else {
			//WMSG1("Got creator chunk: '%s'\n", fCreatorChunk);
		}
	}

	return 0;
}

/*
 * Read the header from a 2IMG file.  Pass in "totalLength" as a sanity check.
 *
 * Returns 0 on success, nonzero on error or invalid header.
 */
int
TwoImgHeader::ReadHeader(GenericFD* pGFD, long totalLength)
{
	DIError dierr;
	unsigned char buf[kOurHeaderLen];

	dierr = pGFD->Read(buf, kOurHeaderLen);
	if (dierr != kDIErrNone)
		return -1;

	if (UnpackHeader(buf, totalLength) != 0)
		return -1;

	/*
	 * Extract the comment, if any.
	 */
	if (fCmtOffset > 0 && fCmtLen > 0) {
		if (GetChunk(pGFD, fCmtOffset - kOurHeaderLen, fCmtLen,
			(void**) &fComment) != 0)
		{
			WMSG0("Throwing comment away\n");
			fCmtLen = 0;
			fCmtOffset = 0;
		} else {
			WMSG1("Got comment: '%s'\n", fComment);
		}
	}

	/*
	 * Extract the creator chunk, if any.
	 */
	if (fCreatorOffset > 0 && fCreatorLen > 0) {
		if (GetChunk(pGFD, fCreatorOffset - kOurHeaderLen, fCreatorLen,
			(void**) &fCreatorChunk) != 0)
		{
			WMSG0("Throwing creator chunk away\n");
			fCreatorLen = 0;
			fCreatorOffset = 0;
		} else {
			//WMSG1("Got creator chunk: '%s'\n", fCreatorChunk);
		}
	}

	return 0;
}

/*
 * Grab a chunk of data from a relative offset.
 */
int
TwoImgHeader::GetChunk(GenericFD* pGFD, di_off_t relOffset, long len,
	void** pBuf)
{
	DIError dierr;
	di_off_t curPos;

	/* remember current offset */
	curPos = pGFD->Tell();

	/* seek out to chunk and grab it */
	dierr = pGFD->Seek(relOffset, kSeekCur);
	if (dierr != kDIErrNone) {
		WMSG0("2MG seek to chunk failed\n");
		return -1;
	}

	assert(*pBuf == nil);
	*pBuf = new char[len+1];		// one extra, for null termination

	dierr = pGFD->Read(*pBuf, len);
	if (dierr != kDIErrNone) {
		WMSG0("2MG chunk read failed\n");
		delete[] (char*) (*pBuf);
		*pBuf = nil;
		(void) pGFD->Seek(curPos, kSeekSet);
		return -1;
	}

	/* null-terminate, in case this was a string */
	((char*) *pBuf)[len] = '\0';

	/* seek back to where we were */
	(void) pGFD->Seek(curPos, kSeekSet);

	return 0;
}

/*
 * Grab a chunk of data from a relative offset.
 */
int
TwoImgHeader::GetChunk(FILE* fp, di_off_t relOffset, long len,
	void** pBuf)
{
	long curPos;
	int count;

	/* remember current offset */
	curPos = ftell(fp);
	WMSG1("Current offset=%ld\n", curPos);

	/* seek out to chunk and grab it */
	if (fseek(fp, (long) relOffset, SEEK_CUR) == -1) {
		WMSG0("2MG seek to chunk failed\n");
		return errno ? errno : -1;;
	}

	assert(*pBuf == nil);
	*pBuf = new char[len+1];		// one extra, for null termination

	count = fread(*pBuf, len, 1, fp);
	if (!count || ferror(fp) || feof(fp)) {
		WMSG0("2MG chunk read failed\n");
		delete[] (char*) (*pBuf);
		*pBuf = nil;
		(void) fseek(fp, curPos, SEEK_SET);
		clearerr(fp);
		return errno ? errno : -1;;
	}

	/* null-terminate, in case this was a string */
	((char*) *pBuf)[len] = '\0';

	/* seek back to where we were */
	(void) fseek(fp, curPos, SEEK_SET);

	return 0;
}


/*
 * Unpack the 64-byte 2MG header.
 *
 * Performs some sanity checks.  Returns 0 on success, -1 on failure.
 */
int
TwoImgHeader::UnpackHeader(const unsigned char* buf, long totalLength)
{
	fMagic = GetLongBE(&buf[0x00]);
	fCreator = GetLongBE(&buf[0x04]);
	fHeaderLen = GetShortLE(&buf[0x08]);
	fVersion = GetShortLE(&buf[0x0a]);
	fImageFormat = GetLongLE(&buf[0x0c]);
	fFlags = GetLongLE(&buf[0x10]);
	fNumBlocks = GetLongLE(&buf[0x14]);
	fDataOffset = GetLongLE(&buf[0x18]);
	fDataLen = GetLongLE(&buf[0x1c]);
	fCmtOffset = GetLongLE(&buf[0x20]);
	fCmtLen = GetLongLE(&buf[0x24]);
	fCreatorOffset = GetLongLE(&buf[0x28]);
	fCreatorLen = GetLongLE(&buf[0x2c]);
	fSpare[0] = GetLongLE(&buf[0x30]);
	fSpare[1] = GetLongLE(&buf[0x34]);
	fSpare[2] = GetLongLE(&buf[0x38]);
	fSpare[3] = GetLongLE(&buf[0x3c]);

	fMagicStr[0] = (char) (fMagic >> 24);
	fMagicStr[1] = (char) (fMagic >> 16);
	fMagicStr[2] = (char) (fMagic >> 8);
	fMagicStr[3] = (char) fMagic;
	fMagicStr[4] = '\0';
	fCreatorStr[0] = (char) (fCreator >> 24);
	fCreatorStr[1] = (char) (fCreator >> 16);
	fCreatorStr[2] = (char) (fCreator >> 8);
	fCreatorStr[3] = (char) fCreator;
	fCreatorStr[4] = '\0';

    if (fMagic != kMagic) {
        WMSG0("Magic number does not match 2IMG\n");
        return -1;
    }

    if (fVersion > 1) {
        WMSG1("ERROR: unsupported version=%d\n", fVersion);
		return -1;		// bad header until I hear otherwise
    }

	if (fFlags & kDOSVolumeSet)
		fDOSVolumeNum = fFlags & kDOSVolumeMask;

	DumpHeader();

	/* fix broken 'WOOF' images from Sweet-16 */
	if (fCreator == kCreatorSweet16 && fDataLen == 0 &&
		fImageFormat != kImageFormatNibble)
	{
		fDataLen = fNumBlocks * kBlockSize;
		WMSG1("NOTE: fixing zero dataLen in 'WOOF' image (set to %ld)\n",
			fDataLen);
	}

	/*
	 * Perform some sanity checks.
	 */
	if (fImageFormat != kImageFormatNibble &&
        fNumBlocks * kBlockSize != fDataLen)
    {
		WMSG2("numBlocks/dataLen mismatch (%ld vs %ld)\n",
			fNumBlocks * kBlockSize, fDataLen);
		return -1;
	}
	if (fDataLen + fDataOffset > totalLength) {
		WMSG3("Invalid dataLen/offset/fileLength (dl=%ld, off=%ld, tlen=%ld)\n",
			fDataLen, fDataOffset, totalLength);
		return -1;
	}
    if (fImageFormat < kImageFormatDOS || fImageFormat > kImageFormatNibble) {
        WMSG1("Invalid image format %ld\n", fImageFormat);
        return -1;
    }

	if (fCmtOffset > 0 && fCmtOffset < fDataOffset + fDataLen) {
		WMSG2("2MG comment is inside the data section (off=%ld, data end=%ld)\n",
			fCmtOffset, fDataOffset+fDataLen);
		DebugBreak();
		// ignore the comment
		fCmtOffset = 0;
		fCmtLen = 0;
	}
	if (fCreatorOffset > 0 && fCreatorLen > 0) {
		long prevEnd = fDataOffset + fDataLen + fCmtLen;

		if (fCreatorOffset < prevEnd) {
			WMSG2("2MG creator chunk is inside prev data (off=%ld, data end=%ld)\n",
				fCreatorOffset, prevEnd);
			DebugBreak();
			// ignore the creator chunk
			fCreatorOffset = 0;
			fCreatorLen = 0;
		}
	}

    return 0;
}

/*
 * Write the header to a 2IMG file.
 *
 * Returns 0 on success, or an errno value on failure.
 */
int
TwoImgHeader::WriteHeader(FILE* fp) const
{
	unsigned char buf[kOurHeaderLen];

	PackHeader(buf);
	if (fwrite(buf, kOurHeaderLen, 1, fp) != 1)
		return errno ? errno : -1;
	return 0;
}

/*
 * Write the header to a 2IMG file.
 *
 * Returns 0 on success, or an errno value on failure.
 */
int
TwoImgHeader::WriteHeader(GenericFD* pGFD) const
{
	unsigned char buf[kOurHeaderLen];

	PackHeader(buf);

	if (pGFD->Write(buf, kOurHeaderLen) != kDIErrNone)
		return -1;

	return 0;
}

/*
 * Write the footer.  File must be seeked to end of data chunk.
 */
int
TwoImgHeader::WriteFooter(FILE* fp) const
{
	WMSG1("Writing footer at offset=%ld\n", (long) ftell(fp));

	if (fCmtLen) {
		fwrite(fComment, fCmtLen, 1, fp);
	}
	if (fCreatorLen) {
		fwrite(fCreatorChunk, fCreatorLen, 1, fp);
	}
	if (ferror(fp))
		return errno ? errno : -1;

	return 0;
}


/*
 * Write the footer.  File must be seeked to end of data chunk.
 */
int
TwoImgHeader::WriteFooter(GenericFD* pGFD) const
{
	WMSG1("Writing footer at offset=%ld\n", (long) pGFD->Tell());

	if (fCmtLen) {
		if (pGFD->Write(fComment, fCmtLen) != kDIErrNone)
			return -1;
	}
	if (fCreatorLen) {
		if (pGFD->Write(fCreatorChunk, fCreatorLen) != kDIErrNone)
			return -1;
	}

	return 0;
}

/*
 * Pack the header values into a 64-byte buffer.
 */
void
TwoImgHeader::PackHeader(unsigned char* buf) const
{
	if (fCmtLen > 0 && fCmtOffset == 0) {
		assert(false);
	}
	if (fCreatorLen > 0 && fCreatorOffset == 0) {
		assert(false);
	}

	PutLongBE(&buf[0x00], fMagic);
	PutLongBE(&buf[0x04], fCreator);
	PutShortLE(&buf[0x08], fHeaderLen);
	PutShortLE(&buf[0x0a], fVersion);
	PutLongLE(&buf[0x0c], fImageFormat);
	PutLongLE(&buf[0x10], fFlags);
	PutLongLE(&buf[0x14], fNumBlocks);
	PutLongLE(&buf[0x18], fDataOffset);
	PutLongLE(&buf[0x1c], fDataLen);
	PutLongLE(&buf[0x20], fCmtOffset);
	PutLongLE(&buf[0x24], fCmtLen);
	PutLongLE(&buf[0x28], fCreatorOffset);
	PutLongLE(&buf[0x2c], fCreatorLen);
	PutLongLE(&buf[0x30], fSpare[0]);
	PutLongLE(&buf[0x34], fSpare[1]);
	PutLongLE(&buf[0x38], fSpare[2]);
	PutLongLE(&buf[0x3c], fSpare[3]);
}

/*
 * Dump the contents of an ImgHeader.
 */
void
TwoImgHeader::DumpHeader(void) const
{
	WMSG0("--- header contents:\n");
	WMSG2("\tmagic         = '%s' (0x%08lx)\n", fMagicStr, fMagic);
	WMSG2("\tcreator       = '%s' (0x%08lx)\n", fCreatorStr, fCreator);
	WMSG1("\theaderLen     = %d\n", fHeaderLen);
	WMSG1("\tversion       = %d\n", fVersion);
	WMSG1("\timageFormat   = %ld\n", fImageFormat);
	WMSG1("\tflags         = 0x%08lx\n", fFlags);
	WMSG1("\t  locked      = %s\n",
		(fFlags & kFlagLocked) ? "true" : "false");
	WMSG2("\t  DOS volume  = %s (%ld)\n",
		(fFlags & kDOSVolumeSet) ? "true" : "false",
		fFlags & kDOSVolumeMask);
	WMSG1("\tnumBlocks     = %ld\n", fNumBlocks);
	WMSG1("\tdataOffset    = %ld\n", fDataOffset);
	WMSG1("\tdataLen       = %ld\n", fDataLen);
	WMSG1("\tcmtOffset     = %ld\n", fCmtOffset);
	WMSG1("\tcmtLen        = %ld\n", fCmtLen);
	WMSG1("\tcreatorOffset = %ld\n", fCreatorOffset);
	WMSG1("\tcreatorLen    = %ld\n", fCreatorLen);
	WMSG0("\n");
}
