/***************************************************************************
    mmcpm - List and extract files from CP/M 2.2 disk image files

    begin     : 10/11/2006
    copyright : (c) 2006 by Michael Minn
    email     : See http://www.michaelminn.com

    Code in this file is intended to be platform independent. Please 
    do not use references to functions other than standard POSIX.

    This program is free software; you can redistribute it and/or modify
    it under the terms of version 2 of the GNU General Public License as 
    published by the Free Software Foundation (see COPYING or www.gnu.org).

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

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>

/* See README.html for explanation of these configuration constants */

#define SECTOR_SIZE		512
#define SECTORS_PER_TRACK	10
#define BYTES_PER_SIDE		179200
#define ALLOCATION_BLOCK_SIZE	2048
#define SECTOR_SKEW		2
#define SIDE_INTERLEAVE		1
#define SYSTEM_TRACKS		2
#define DIRECTORY_SIZE		2048
#define ONES_COMPLIMENT		1
#define BUFFER_SIZE		2000000
#define EXAMINE_FILE		"mmcpm.img"

int print_syntax()
{
	fprintf(stderr, "SYNTAX: mmcpm [copy | examine] <image_file>\n");
	exit(-1);
}

int main(int argc, char **argv)
{
	unsigned char data[BUFFER_SIZE];
	char *filename = NULL;
	int copy_files = 0;
	int examine = 0;

	/* command line arguments */
	if (argc == 2)
		filename = argv[1];
	else if ((argc == 3) && (argv[1][0] == 'c'))
	{
		copy_files = 1;
		filename = argv[2];
	}
	else if ((argc == 3) && (argv[1][0] == 'e'))
	{
		examine = 1;
		filename = argv[2];
	}
	else
		print_syntax();

	/* Read the image file */
	int descriptor = 0;
	if ((descriptor = open(filename, O_RDONLY)) < 0)
		return fprintf(stderr, "%s: %s\n", strerror(errno), filename);

	int length = 0;
	if ((length = read(descriptor, data, BUFFER_SIZE)) < 0)
		return fprintf(stderr, "%s: %s\n", strerror(errno), filename);

	close(descriptor);

	/* If the image exceeds the maximum for a single-sided disk, it must be double-sided */
	int sides = 1;
	if (length > BYTES_PER_SIDE)
		sides = 2;

	/* The SuperBrain stores data with the bits inverted */
	if (ONES_COMPLIMENT)
	{
		int x;
		for (x = 0; x < length; ++x)
			data[x] = ~data[x];
	}

	/* Some images are stored with sides interleaved (cyl 1 side 1, cyl 1-side 2, cyl 2 side 1, cyl 2 side 2...) */
	/* even though data is used as if all of side 1 is together and all of side 2 follows */
	/* This code de-interleaves data, if needed */
	if ((sides > 1) && SIDE_INTERLEAVE)
	{
		int cylinder = 0;
		int track_size = SECTOR_SIZE * SECTORS_PER_TRACK;
		unsigned char buffer[BUFFER_SIZE];

		memcpy(buffer, data, length);
		for (cylinder = 0; cylinder < length; cylinder += (track_size * 2))
		{
		// fprintf(stderr, "0x%x > 0x%x 0x%x\n", cylinder, (cylinder / 2), (cylinder / 2) + (length / 2));
			memcpy(data + (cylinder / 2), buffer + cylinder, track_size);
			memcpy(data + (length / 2) + (cylinder / 2), buffer + cylinder + track_size, track_size);
		}
	}

	/* Some old, slow CPUs and buses couldn't handle data at the rate it came off the disk. */
	/* So sectors were skewed to provide time for processing a sector before the next one came around. */
	/* A skew of 2 with 10 sectors per track means data stored in sectors in order 0 2 4 6 8 1 3 5 7 9 */
	/* This code deskews the data, if needed */
	if (SECTOR_SKEW > 0)
	{
		int track = 0;
		int sector = 0;
		int track_size = SECTOR_SIZE * SECTORS_PER_TRACK;
		unsigned char buffer[BUFFER_SIZE];
		memcpy(buffer, data, length);

		for (track = 0; track < length; track += track_size)
			for (sector = 0; sector < SECTORS_PER_TRACK; ++sector)
			{
				int source = sector * SECTOR_SKEW;
				while (source >= SECTORS_PER_TRACK)
					source = source + 1 - SECTORS_PER_TRACK;

				memcpy(data + track + (sector * SECTOR_SIZE), 
					buffer + track + (source * SECTOR_SIZE), SECTOR_SIZE);
			}

	}

	/* Performs a hex dump of the modified image and, also, saves it to a file */
	if (examine)
	{
		int x = 0;
		for (x = 0; x < length; x += 16)
		{
			int y = 0;
			printf("%06x ", x);
			for (y = 0; y < 16; ++y)
				printf("%02x ", data[x + y]);
			for (y = 0; y < 16; ++y)
				if ((data[x + y] >= ' ') && (data[x + y] <= 127))
					printf("%c", data[x + y]);
				else
					printf(".");
			printf("\n");
		}

		if ((descriptor = open(EXAMINE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644)) > 0)
		{
			write(descriptor, data, length);
			close(descriptor);
			printf("Modified image saved to %s\n", EXAMINE_FILE);
		}
	}

	/* Traverse the directory entries */
	int directory_start = SYSTEM_TRACKS * SECTORS_PER_TRACK * SECTOR_SIZE;
	int directory_end = directory_start + DIRECTORY_SIZE;
	int extent = 0;
	for (extent = directory_start; extent < directory_end; extent += 32)
		if ((data[extent + 1] != 0xe5))
		{
			/* CP/M names are stored as 8 name and 3 extension char, padded with spaces */
			int despace = 0;
			char unix_name[13];
			sprintf(unix_name, "%.8s.%.3s", data + extent + 1, data + extent + 9);
			for (despace = 0; unix_name[despace]; ++despace)
			{
				unix_name[despace] = tolower(unix_name[despace]);
				if (unix_name[despace] == ' ')
				{
					memmove(unix_name + despace, unix_name + despace + 1, strlen(unix_name + despace));
					--despace;
				}
			}

			int file_size = (data[extent + 12] * 16384) + (128 * data[extent + 15]);
			printf("%02x %6d %12s ", data[extent], file_size, unix_name);

			/* Open output file if copying files */
			descriptor = -1;
			if (copy_files)
				if ((descriptor = open(unix_name, O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0)
					fprintf(stderr, "%s: %s\n", unix_name, strerror(errno));

			/* Traverse list of blocks */
			int block;
			for (block = 0; block < 16; ++block)
				if (data[extent + 16 + block] > 0)
				{
					int block_number = data[extent + 16 + block] - 1;
					int offset = (SECTOR_SIZE * SECTORS_PER_TRACK * SYSTEM_TRACKS) + DIRECTORY_SIZE + 
						(ALLOCATION_BLOCK_SIZE	* block_number);

					int block_size = ALLOCATION_BLOCK_SIZE;
					if (block_size > (file_size - (block * ALLOCATION_BLOCK_SIZE)))
						block_size = (file_size - (block * ALLOCATION_BLOCK_SIZE));
						
					printf("%02x ", block_number);
					if (offset > length)
						printf("(Invalid block) ");
					else if ((descriptor > 0) && (block_size > 0))
						write(descriptor, data + offset, ALLOCATION_BLOCK_SIZE);
				}
			printf("\n");
		}

	return 0;
}

