// OpenCP Module Player
// copyright (c) '94-'98 Niklas Beisert <nbeisert@physik.tu-muenchen.de>
//
// SIDPlay PSID file loader
//
// revision history: (please note changes here)
//  -kb980717  Tammo Hinrichs <opencp@gmx.net>
//    -first release

//
// 1997/05/11 11:29:06
//

#include <alloc.h>
#include <string.h>
#include "binfile.h"
#include "psid_.h"


static const char text_notEnoughMemory[] = "ERROR: Not enough free memory";
static const char text_format[] = "PlaySID one-file format (PSID)";
static const char text_psidTruncated[] = "ERROR: PSID file is most likely truncated";

struct psidHeader
{
	//
	// All values in big-endian order.
	//
	char id[4];          // 'PSID'
	ubyte version[2];    // 0x0001 or 0x0002
	ubyte data[2];       // 16-bit offset to binary data in file
	ubyte load[2];       // 16-bit C64 address to load file to
	ubyte init[2];       // 16-bit C64 address of init subroutine
	ubyte play[2];       // 16-bit C64 address of play subroutine
	ubyte songs[2];      // number of songs
	ubyte start[2];      // start song (1-256 !)
	ubyte speed[4];      // 32-bit speed info
						 // bit: 0=50 Hz, 1=CIA 1 Timer A (default: 60 Hz)
	char name[32];       // ASCII strings, 31 characters long and
	char author[32];     // terminated by a trailing zero
	char copyright[32];  //
	ubyte flags[2];      // only version 0x0002
	ubyte startPage;     // only version 0x0002
	ubyte pageLength;    // only version 0x0002
	ubyte secondSidAddress;		// only version 0x0002
	ubyte thirdSidAddress;      // only version 0x0002
};


static uword decodeSidAddress(ubyte sidAddress)
{
	if ( ((sidAddress & 0x01) == 0 ) &&
			( (sidAddress >= 0x20 && sidAddress < 0x80) ||
			(sidAddress >= 0xe0 && sidAddress < 0xfe) ) )
	{
		return 0xd000 | (sidAddress << 4);
	}
	else
	{
		return 0;
	}
}


char sidTune::PSID_fileSupport(binfile &f)
{
	struct psidHeader header;
	binfilepos fileLen;

	// Remove any format description or format error string.
	info.formatString = 0;

	fileLen = f.length();
	f.seek(0);
	if (f.read((void huge *)&header, sizeof(psidHeader)) < 6) {
		return false;
	}

	// Now it is safe to access the first bytes.
	// Require a valid ID and version number.
	if ( (readBEdword((ubyte huge *)header.id) != 0x50534944)  // "PSID" ?
		|| (readBEword(header.version) >= 4) )
	{
		return false;
	}
	// Due to security concerns, input must be at least as long as version 1
	// plus C64 load address data. That is the area which will be accessed.
	if ( fileLen < (sizeof(psidHeader)+2) )
	{
		info.formatString = text_psidTruncated;
		return false;
	}

	fileOffset = readBEword(header.data);
	info.loadAddr = readBEword(header.load);
	info.initAddr = readBEword(header.init);
	info.playAddr = readBEword(header.play);
	info.songs = readBEword(header.songs);
	info.startSong = readBEword(header.start);

	if (info.songs > classMaxSongs)
	{
		info.songs = classMaxSongs;
	}

	// Create the speed/clock setting table.
	udword oldStyleSpeed = readBEdword(header.speed);
	convertOldStyleSpeedToTables(oldStyleSpeed);

	info.musPlayer = false;
	if ( readBEword(header.version) >= 2 )
	{
		uword flags = readBEword(header.flags);
		if ((flags & 1) == 1)
		{
			info.musPlayer = true;
		}

		if ((flags & 2) == 2)
		{
			// PlaySID specific - not supported
			return false;
		}

		switch ((flags & 0xc) >> 2)
		{
			case 1:
				info.clockSpeed = SIDTUNE_CLOCK_PAL;
				break;
			case 2:
				info.clockSpeed = SIDTUNE_CLOCK_NTSC;
				break;
		}
	}

	if ( info.loadAddr == 0 )
	{
		ubyte loadAddrBuf[2];
		if ((f.seek(fileOffset) != fileOffset) ||
				(f.read((void far *)&loadAddrBuf, 2) != 2)) {
			info.formatString = text_psidTruncated;
			return false;
		}
		info.loadAddr = readEndian(loadAddrBuf[1], loadAddrBuf[0]);
		fileOffset += 2;
	}
	if ( info.initAddr == 0 )
	{
		info.initAddr = info.loadAddr;
	}
	if ( info.startSong == 0 )
	{
		info.startSong = 1;
	}

	// Now adjust MUS songs.
	if ( info.musPlayer )
	{
		info.loadAddr = 0x1000;
		info.initAddr = 0xc7b0;
		info.playAddr = 0;
	}

	info.sidChips = 1;
	info.secondSidAddress = 0;
	info.thirdSidAddress = 0;
	if (readBEword(header.version) >= 3)
	{
		info.secondSidAddress = decodeSidAddress(header.secondSidAddress);
		if (info.secondSidAddress != 0)
		{
			info.sidChips++;
		}

		if (readBEword(header.version) == 4)
		{
			info.thirdSidAddress =
				decodeSidAddress(header.thirdSidAddress);
			if (info.thirdSidAddress != 0)
			{
				info.sidChips++;
			}
		}
	}

	// Correctly terminate the info strings.
	header.name[31] = 0;
	header.author[31] = 0;
	header.copyright[31] = 0;

	// Copy info strings, so they will not get lost.
	_fstrcpy( &infoString[0][0], header.name );
	info.nameString = &infoString[0][0];
	info.infoString[0] = &infoString[0][0];
	_fstrcpy( &infoString[1][0], header.author );
	info.authorString = &infoString[1][0];
	info.infoString[1] = &infoString[1][0];
	_fstrcpy( &infoString[2][0], header.copyright );
	info.copyrightString = &infoString[2][0];
	info.infoString[2] = &infoString[2][0];
	info.numberOfInfoStrings = 3;

	info.formatString = text_format;
	info.dataFileLen = fileLen;

	return true;
}

