/*

disk2rom.c - prepare ROM image

*/

/*...sincludes:0:*/
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
/*...e*/

static char progname[] = "disk2rom";

/*...sfatal:0:*/
static void fatal(const char *fmt, ...)
	{
	va_list	vars;
	char s[256+1];
	va_start(vars, fmt);
	vsprintf(s, fmt, vars);
	va_end(vars);
	fprintf(stderr, "%s: %s\n", progname, s);
	exit(1);
	}
/*...e*/
/*...susage:0:*/
static void usage(void)
	{
	fprintf(stderr, "usage: %s file.mfloppy file.rdi\n", progname);
	fprintf(stderr, "flags: file.mfloppy  input CP/M filesystem image\n");
	fprintf(stderr, "       file.rdi      output ROM Disc Image\n");
	exit(1);
	}
/*...e*/
/*...smain:0:*/
/*...scompress:0:*/
/*...srun_length:0:*/
static unsigned run_length(const unsigned char *buf, unsigned length)
	{
	unsigned char b = buf[0];
	unsigned l = 1;
	while ( l < length && l < 127 && b == buf[l] )
		++l;
	return l;
	}
/*...e*/
/*...slit_length:0:*/
static unsigned lit_length(const unsigned char *buf, unsigned length)
	{
	unsigned l = 1;
	while ( l < length && l < 127 && buf[l-1] != buf[l] )
		++l;
	return l;
	}
/*...e*/

static unsigned compress(const unsigned char *src, unsigned length, unsigned char *dst)
	{
	int n = 0;
	while ( length > 0 )
		{
		unsigned l = run_length(src, length);
		if ( l > 1 )
			{
			dst[n++] = (unsigned char)(-(signed char)l);
			dst[n++] = src[0];
			src += l;
			}
		else
			{
			unsigned i;
			l = lit_length(src, length);
			dst[n++] = (unsigned char) l;
			for ( i = 0; i < l; i++ )
				dst[n++] = *src++;
			}
		length -= l;
		}
/* not needed, decoder counts bytes
	dst[n++] = 0x00;
*/
	return n;
	}
/*...e*/

#define	LARGEST_FS_SIZE (8*1024*1024)
#define	SECTOR_SIZE 128
#define	ROM_SIZE_LN2 13
#define	ROM_SIZE (1<<ROM_SIZE_LN2)
#define	LARGEST_ROM_CHIP (512*1024)
#define	LARGEST_RDI (LARGEST_ROM_CHIP-ROM_SIZE)

#define	INDEX_ENTRIES_PER_ROM (ROM_SIZE/3) /* =2730 */
#define	LARGEST_FS_SIZE_SUPPORTED (INDEX_ENTRIES_PER_ROM*2*SECTOR_SIZE)

int main(int argc, char *argv[])
	{
	FILE *fp;
	long fs_size;
	unsigned char *rdi;
	unsigned n_sectors, i, n_dupes = 0;
	unsigned rdip = 0;

	if ( argc < 3 )
		usage();

	if ( (fp = fopen(argv[1], "rb")) == NULL )
		fatal("can't open input file %s", argv[1]);
	fseek(fp, 0, SEEK_END);
	fs_size = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	if ( fs_size > LARGEST_FS_SIZE )
		fatal("filesystem image is larger than largest CP/M filesystem size (%lu)",
			(unsigned long) LARGEST_FS_SIZE);
	if ( fs_size > LARGEST_FS_SIZE_SUPPORTED )
		fatal("filesystem image is larger than this program can cope with (%lu)",
			(unsigned long) LARGEST_FS_SIZE_SUPPORTED);

	/* allocate extra SECTOR_SIZE to prevent the memcmp below ever failing */
	if ( (rdi = (unsigned char *) malloc(LARGEST_RDI+SECTOR_SIZE)) == NULL )
		fatal("can't allocate space for ROM Disc Image");

	/* two functions:
	   1. magic number at the start.
	   2. ensure no index entry straddles the first two subpages,
	      by prefixing two bytes, as 2+INDEX_ENTRIES_PER_ROM*3 = ROM_SIZE.
	      640KB type 07 image will have 5120 sectors. */
	rdi[0] = 0xb0;
	rdi[1] = 0x0b;

	n_sectors = (unsigned) (fs_size / SECTOR_SIZE);
	rdip = 2+n_sectors*3;

	for ( i = 0; i < n_sectors; i++ )
		{
		unsigned char u_sector[SECTOR_SIZE];
		unsigned char c_sector[SECTOR_SIZE*2+1];
		unsigned length;
		unsigned char *p;
		unsigned char comp_flag;
		unsigned j;
		if ( fread(u_sector, 1, SECTOR_SIZE, fp) != SECTOR_SIZE )
			fatal("can't read sector %u", i);
		length = compress(u_sector, SECTOR_SIZE, c_sector);
		if ( length < SECTOR_SIZE )
			{
			comp_flag = 0x80;
			p = c_sector;
			}
		else
			{
			comp_flag = 0x00;
			p = u_sector;
			length = SECTOR_SIZE;
			}
		/* see if we've had the same content before */
		for ( j = 0; j < i; j++ )
			if ( (rdi[j*3]&0x80) == comp_flag )
				{
				unsigned rdip_j = ((unsigned)(rdi[2+j*3  ]&~0x80)<<ROM_SIZE_LN2)
				                +  (unsigned) rdi[2+j*3+1]
				                + ((unsigned)(rdi[2+j*3+2])<<8);
				if ( !memcmp(rdi+rdip_j, p, length) )
					break;
				}
		if ( j < i )
			{
			rdi[2+i*3  ] = rdi[2+j*3  ];
			rdi[2+i*3+1] = rdi[2+j*3+1];
			rdi[2+i*3+2] = rdi[2+j*3+2];
			++n_dupes;
			}
		else
			{
			if ( (rdip&~(ROM_SIZE-1)) != ((rdip+length)&~(ROM_SIZE-1)) )
				/* no sector is allowed to straddle ROMs */
				{
				unsigned o = rdip&(ROM_SIZE-1);
				memset(rdi+rdip, 0xea, ROM_SIZE-o); // ea for "empty area"
				rdip += (ROM_SIZE-o);
				}
			if ( rdip == LARGEST_RDI )
				fatal("reached largest ROM Disc Image size after reading %u sectors (%uKB)", i, (i*128)/1024);
			rdi[2+i*3  ] = (unsigned char) (comp_flag|(rdip>>ROM_SIZE_LN2));
			rdi[2+i*3+1] = (unsigned char) (rdip&0xff);
			rdi[2+i*3+2] = (unsigned char) ((rdip&(ROM_SIZE-1))>>8);
			memcpy(rdi+rdip, p, length);
			rdip += length;
			}
		}
	fclose(fp);

	if ( rdip&(ROM_SIZE-1) )
		/* pad to complete 8KB ROM page */
		{
		unsigned o = rdip&(ROM_SIZE-1);
		memset(rdi+rdip, 0xcd, ROM_SIZE-o); // cd for "compressed disc"
		rdip += (ROM_SIZE-o);
		}

	if ( (fp = fopen(argv[2], "wb")) == NULL )
		fatal("can't create RDI file %s", argv[2]);
	if ( fwrite(rdi, 1, rdip, fp) != rdip )
		fatal("can't write RDI file %s", argv[2]);
	fclose(fp);

	printf("%u sectors, %luKB reduced to %luKB, %u dupes\n",
		n_sectors,
		((unsigned)fs_size+1023)/1024,
		(rdip+1023)/1024,
		n_dupes
		);

	return 0;
	}
/*...e*/
