/* PAK file loader
 *
 * TODO: Support .PAK files generated by PC-Dragon
 */

#include "formats/cocopak.h"

/*	PAK files have the following format:
 *
 *	length		(two bytes, little endian)
 *	base address (two bytes, little endian, typically 0xc000)
 *	...data... (size is length)
 *	optional trailer
 *
 *	The format for PAK files just plain bites - the extra info is snapshot info
 *	and it is loaded with internal state specific to Jeff's emulator. What ever
 *	happened to desiging clean file formats that don't need to be changed with
 *	every version of ones program?
 *
 *	For alignment purposes, some 16 bit values are divided into two UINT8s
 *
 *	The PAK file format's trailer defines the state of the machine other then
 *	the core memory.  This trailer comes in many variations.  I have labeled
 *	them in the following way (trailer sizes are in parentheses):
 *
 *		V12		- The version in JeffV's emulator version 1.2
 *		V14		- The version in JeffV's emulator version 1.4	(457)
 *		VOTHER	- From some "other" version of JeffV's emulator
 *		VLITE	- From yet another version of JeffV's emulator	(35)
 *
 *	The following table shows what segments appear in each trailer variant:
 *
 *	Trailer Segment		V12		V14		VOTHER	VLITE
 *	---------------		---		---		------	-----
 *	pak_trailer1		1		1		1		1
 *	pak_trailer2		2
 *	pak_trailer3		3		2		2		2
 *	pak_trailer4		4		3		3
 *	pak_trailer5		5
 *	pak_trailer6		6		5		4
 *	pak_trailer7		7				5
 *	pak_trailer8				6
 *	pak_trailer9		8		7		6
 *	pak_trailer10		9		4		7
 *	pak_trailer11		10		8		8
 *	pak_trailer12				9
 *	pak_trailer13		11				9
 */

/* All versions */
typedef struct {
	char name[33];
} pak_trailer1;

/* Only version 1.2 */
typedef struct {
	UINT8 debug_dumpflag;
	UINT8 debug_disassembleflag;
	UINT16 debug_disassembleaddr;
} pak_trailer2;

/* All versions */
typedef struct {
	UINT16 reg_pc;
} pak_trailer3;

/* All versions except lite */
typedef struct {
	UINT16 reg_x;
	UINT16 reg_y;
	UINT16 reg_u;
	UINT16 reg_s;
	UINT8 dummy_zero1;
	UINT8 reg_dp;
	UINT8 reg_b;
	UINT8 reg_a;
} pak_trailer4;

/* Only version 1.2 */
typedef struct {
	UINT16 debug_unknown;
} pak_trailer5;

/* All versions except lite */
typedef struct {
	UINT8 flags_8086_lsb; /* ?!? */
	UINT8 flags_8086_msb; /* ?!? */
	UINT8 reg_cc;
} pak_trailer6;

/* All versions except 1.4 and lite*/
typedef struct {
	UINT16 lowmem_readseg;
	UINT16 lowmem_writeseg;
	UINT16 himem_readseg;
	UINT16 himem_writeseg;
} pak_trailer7;

/* Only version 1.4 */
typedef struct {
	UINT8 page_status;
	UINT8 rom_status;
	UINT8 io_ff02;
	UINT8 io_ff03;
	UINT8 io_ff22;
} pak_trailer8;

/* All versions except lite */
typedef struct {
	UINT16 video_base;
	UINT16 video_end;
} pak_trailer9;

/* All versions except lite; come before pak_trailer6 in 1.4 */
typedef struct {
	UINT8 dummy_zero2[6];
} pak_trailer10;

/* Used by PC-Dragon; UINT16's are little endian */
typedef struct {
	/* Info */
	UINT8 data1[2];
	UINT8 magic1[2];
	UINT8 checksum;
	UINT8 pak_version;
	UINT8 ext_version;
	UINT8 emu_version;
	UINT8 state_only;
	UINT8 architecture;
	UINT8 rom_start;

	/* Machine state */
	UINT8 irq_cycles_lsb;
	UINT8 irq_cycles_msb;
	UINT8 screen_size;
	UINT8 pia[9];
	UINT8 last_shift;
	UINT8 filemem[8];

	/* Debugger */
	UINT16 last_text_base;
	UINT16 breakpoints[6];
	UINT16 break_operation;
	UINT16 dasm_pcr;
	UINT16 fill_start;
	UINT16 fill_end;
	UINT8 fill_value;
	UINT8 left_window;
	UINT16 temp_break;
	UINT16 break_value;
	UINT8 break_control;
	UINT8 break_type;
	UINT16 internal;
	UINT16 dumpaddr;
} pcd_info1;

/* Used by PC-Dragon; UINT16's are little endian */
typedef struct {
	UINT8 pakdata[65];
	UINT8 dummy1;
	UINT8 background;
	UINT8 foreground;
	UINT8 vmode[2][2][6];
	UINT8 border[2][2];
	UINT8 dummy2;
	UINT16 irq_rate;
	UINT8 servicemem[16];
	UINT8 speed;
	UINT8 lpt_and_swapping;
	UINT8 hardjoy_resol;
} pcd_info2;

/* All versions except lite */
typedef struct {
	UINT8 writeprotect;
	char disk_directory[66];	/* Or could be a pcd_info1 */
	char disk_name[4][32];		/* Or could be a pcd_info2 */
	char pak_directory[66];
	UINT8 crlf;
	UINT8 keyboard_mode;
	UINT8 speed_lsb;
	UINT8 speed_msb;
	UINT8 left_joystick;
	UINT8 right_joystick;
	UINT8 lowercase_lsb;
	UINT8 lowercase_msb;
	UINT8 sound;
	UINT8 artifact;
	UINT8 dragon_rom;
} pak_trailer11;

/* Only version 1.4 */
typedef struct {
	UINT16 joystick_limits[8];
	UINT16 clock;
	UINT8 drive_mode;
	UINT8 volume;
	UINT8 cassette_mode;
	char cassette_directory[66];
	char cassette_name[33];
} pak_trailer12;

/* All versions except 1.4 and lite */
typedef struct {
	UINT8 dummy_zero3[4];
	UINT16 video_base2;
	UINT16 video_end2;
	UINT16 dummy_zero4;
	UINT8 io_ff22;
	UINT8 io_ff02;
	UINT8 io_ff03;
} pak_trailer13;

#define PAK_V12_SIZE	(sizeof(pak_trailer1) + sizeof(pak_trailer2) + \
	sizeof(pak_trailer3) + sizeof(pak_trailer4) + sizeof(pak_trailer5) + \
	sizeof(pak_trailer6) + sizeof(pak_trailer7) + sizeof(pak_trailer9) + \
	sizeof(pak_trailer10) + sizeof(pak_trailer11) + sizeof(pak_trailer13))

#define PAK_V14_SIZE	(sizeof(pak_trailer1) + sizeof(pak_trailer3) + \
	sizeof(pak_trailer4) + sizeof(pak_trailer10) + sizeof(pak_trailer6) + \
	sizeof(pak_trailer8) + sizeof(pak_trailer9) + sizeof(pak_trailer11) + \
	sizeof(pak_trailer12))

#define PAK_VOTHER_SIZE	(sizeof(pak_trailer1) + sizeof(pak_trailer3) + \
	sizeof(pak_trailer4) + sizeof(pak_trailer6) + sizeof(pak_trailer7) + \
	sizeof(pak_trailer9) + sizeof(pak_trailer10) + sizeof(pak_trailer11) + \
	sizeof(pak_trailer13))

#define PAK_VLITE_SIZE (sizeof(pak_trailer1) + sizeof(pak_trailer3))

static UINT16 calculate_sam_video(pak_trailer9 *p9)
{
	UINT16 sam;
	UINT16 video_base;
	UINT16 video_end;

	video_base = LITTLE_ENDIANIZE_INT16(p9->video_base);
	video_end = LITTLE_ENDIANIZE_INT16(p9->video_end);

	sam = (video_base >> 6) & 0x3f8;

	switch(video_end - video_base) {
	case 512:
		sam |= 0;
		break;
	case 1024:
		sam |= 1;
		break;
	case 1536:
	case 2048:
		sam |= 2;
		break;
	case 3072:
		sam |= 4;
		break;
	case 6144:
	default:
		sam |= 6;
		break;
	}

	return sam;
}

int pak_decode_trailer(UINT8 *rawtrailer, int rawtrailerlen, pak_decodedtrailer *trailer)
{
	pak_trailer3 p3;
	pak_trailer4 p4;
	pak_trailer6 p6;
	pak_trailer9 p9;
	pak_trailer11 p11;
	pcd_info1 *pcd1 = NULL;
	pcd_info2 *pcd2 = NULL;

	union {
		pak_trailer8 p8;
		struct {
			pak_trailer7 p7;
			pak_trailer13 p13;
		} s;
	} u1;

	switch(rawtrailerlen) {
	case PAK_V12_SIZE:
	case PAK_V14_SIZE:
	case PAK_VOTHER_SIZE:
	case PAK_VLITE_SIZE:
		break;
	default:
		return 1;
	}

	memset(&p3, 0, sizeof(p3));
	memset(&p4, 0, sizeof(p4));
	memset(&p6, 0, sizeof(p6));
	memset(&p9, 0, sizeof(p9));
	memset(&u1, 0, sizeof(u1));

	rawtrailer += sizeof(pak_trailer1);

	if (rawtrailerlen == PAK_V12_SIZE) {
		rawtrailer += sizeof(pak_trailer2);
	}

	memcpy(&p3, rawtrailer, sizeof(pak_trailer3));
	rawtrailer += sizeof(pak_trailer3);

	if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&p4, rawtrailer, sizeof(pak_trailer4));
		rawtrailer += sizeof(pak_trailer4);
	}

	if (rawtrailerlen == PAK_V14_SIZE) {
		rawtrailer += sizeof(pak_trailer10);
	}
	else if (rawtrailerlen == PAK_V12_SIZE) {
		rawtrailer += sizeof(pak_trailer5);
	}

	if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&p6, rawtrailer, sizeof(pak_trailer6));
		rawtrailer += sizeof(pak_trailer6);
	}

	if (rawtrailerlen == PAK_V14_SIZE) {
		memcpy(&u1.p8, rawtrailer, sizeof(pak_trailer8));
		rawtrailer += sizeof(pak_trailer8);
	}
	else if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&u1.s.p7, rawtrailer, sizeof(pak_trailer7));
		rawtrailer += sizeof(pak_trailer7);
	}

	if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&p9, rawtrailer, sizeof(pak_trailer9));
		rawtrailer += sizeof(pak_trailer9);
	}

	if ((rawtrailerlen != PAK_V14_SIZE) && (rawtrailerlen != PAK_VLITE_SIZE)) {
		rawtrailer += sizeof(pak_trailer10);
	}

	if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&p11, rawtrailer, sizeof(pak_trailer11));
		rawtrailer += sizeof(pak_trailer11);

		/* Is this a fancy image with PC-Dragon Info in it? */
		pcd1 = (pcd_info1 *) p11.disk_directory;
		if ((pcd1->magic1[0] == 0xAB) && (pcd1->magic1[1] == 0xBA)) {
			/* Yes it is! */
			/* Note! I should be checking the checksum */
			pcd2 = (pcd_info2 *) p11.disk_name;
		}
		else {
			/* No it is not */
			pcd1 = NULL;
		}
	}

	if (rawtrailerlen == PAK_V14_SIZE) {
		rawtrailer += sizeof(pak_trailer12);
	}
	else if (rawtrailerlen != PAK_VLITE_SIZE) {
		memcpy(&u1.s.p13, rawtrailer, sizeof(pak_trailer13));
		rawtrailer += sizeof(pak_trailer13);
	}

	trailer->reg_pc = LITTLE_ENDIANIZE_INT16(p3.reg_pc);
	trailer->reg_x = LITTLE_ENDIANIZE_INT16(p4.reg_x);
	trailer->reg_y = LITTLE_ENDIANIZE_INT16(p4.reg_y);
	trailer->reg_u = LITTLE_ENDIANIZE_INT16(p4.reg_u);
	trailer->reg_s = LITTLE_ENDIANIZE_INT16(p4.reg_s);
	trailer->reg_dp = p4.reg_dp;
	trailer->reg_b = p4.reg_b;
	trailer->reg_a = p4.reg_a;
	trailer->reg_cc = p6.reg_cc;

	trailer->pia[0] = 0x00;
	trailer->pia[1] = 0x34;
	trailer->pia[4] = 0x02;
	trailer->pia[5] = 0x34;
	trailer->pia[7] = 0x3c;

	switch(rawtrailerlen) {
	case PAK_V14_SIZE:
		trailer->pia[2] = u1.p8.io_ff02;
		trailer->pia[3] = u1.p8.io_ff03;
		trailer->pia[6] = u1.p8.io_ff22;
		trailer->sam = (u1.p8.rom_status == 0xdf) ? 0xE000 : 0x6000;
		trailer->sam |= calculate_sam_video(&p9);
		break;

	case PAK_VLITE_SIZE:
		/* This is a "lite" format that gives no trailer information except
		 * for the program counter... I have to set everything up as default
		 * including the stack pointer... arg this sucks...
		 */
		trailer->pia[2] = 0xff;
		trailer->pia[3] = 0x34;
		trailer->pia[6] = 0x00;
		trailer->sam = 0x6008;
		trailer->reg_s = 0x3d7;
		break;

	default:
		trailer->pia[2] = u1.s.p13.io_ff02;
		trailer->pia[3] = u1.s.p13.io_ff03;
		trailer->pia[6] = u1.s.p13.io_ff22;
		trailer->sam = (u1.s.p7.himem_readseg == 0) ? 0xE000 : 0x6000;
		trailer->sam |= calculate_sam_video(&p9);
		break;
	}
	return 0;
}

