#include <stdlib.h>
#include <stdio.h>

#define FALSE (0)
#define TRUE (!FALSE)

/* Force stdin into binary mode if running cygwin */
#ifdef __CYGWIN__
#include <fcntl.h>
#include <io.h>
void stdin_setup() {
	if (setmode(0,O_BINARY)==EOF) {
		fprintf(stderr,"\nImage read error.\n");
		exit(1);
	}
}
#else
void stdin_setup() {}
#endif

#define TOTAL_DISK_BLOCKS (8*2*80)
#define SECTOR_SIZE (0x200)
#define DISK_SIZE (TOTAL_DISK_BLOCKS * SECTOR_SIZE)
#define DIR_ENTRY_SIZE (12)

unsigned char disk[DISK_SIZE];

void check_bounds(int block) {
       if (block < 0 || block >= DISK_SIZE) {
		fprintf(stderr,"\nImage file error. Block %4x\n",(int)block);
		exit(1);
	}
}

int tolong(int offset) {
	unsigned int l = 0; int i;
	check_bounds(l+3);
	for (i=0;i<4;i++)
		{ l= (l<<8)| disk[offset+i]; }
	return  l;
}

struct tds_file {
	int first_file_block;
	int current_file_block;
	int offset_in_current_file_block;
	int data_blocks_remaining;
};

void init_fb(struct tds_file * f, int file_block) {
	check_bounds(file_block);
	f->first_file_block = file_block;
	f->current_file_block = file_block;
	f -> data_blocks_remaining=tolong(file_block+32);
	f -> offset_in_current_file_block = 36;
}

int get_next_sector (struct tds_file *f) { 
	/* The count of valid blocks in the current file block is at offset 32 of the
	 * file block.
	 * The individual data sectors of the file start at offset 36. 
	 * If we have to chain to further file blocks, the pointer to the
	 * next one is at offset 12, otherwise the long here is -1 */
	int next_sector;
	if ((f -> data_blocks_remaining)<=0)
		if((disk[(f -> current_file_block)+12] & 0xff) == 0xff)
			return (-1);
		else {
			f -> current_file_block = tolong((f -> current_file_block)+12)*SECTOR_SIZE;
			check_bounds(f -> current_file_block);
			f -> data_blocks_remaining=tolong(f->current_file_block+32);
			f -> offset_in_current_file_block = 36;
		}
	next_sector = tolong(f -> current_file_block + 
			f -> offset_in_current_file_block)*SECTOR_SIZE;
	check_bounds(next_sector);
	f -> offset_in_current_file_block += 4;
	f -> data_blocks_remaining -= 1;
	return next_sector;
}

void write_error() {
	fprintf(stderr,"\nFile write error\n");
	exit(1);
}
void usage(char * n) {
          fprintf(stderr, "Usage: %s [-d] < image\n"
                          "\t-d\twrite out files in the image\n",n);
          exit(1);
}

main(int argc, char * argv[]) {
	struct tds_file root, current;
	int write_files;
        char * prog_name = argc>0?argv[0]:"tdsfs";
        /* Parse arguments */
        write_files = FALSE;
        while (--argc > 0) {
                argv++;
                if (strlen(*argv) != 2 || **argv != '-')
                        usage(prog_name);
                switch ((*argv)[1]) {
                        case 'd':
                                write_files = TRUE;
                                break;
                        default:
                                usage(prog_name);
                                break;
                }
        }
	/* Set stdin to binary mode if appropriate and read in the image */
        stdin_setup();
	if(fread(disk,1,DISK_SIZE,stdin)!=DISK_SIZE) {
		fprintf(stderr,"\nImage read error.\n");
		exit(1);
	}	
	/* The root block is half way into the disk. The long at offset 24 points
	 * to a file block for the root directory. The long at offset 28 seems to
	 * point to some sort of free block bitmap */
	init_fb(&root, tolong((TOTAL_DISK_BLOCKS/2)*SECTOR_SIZE + 24)*SECTOR_SIZE);
	/* Read the root directory */
	{
		int offset_in_current_root_sector = 0;
		int current_root_sector;
		while ((current_root_sector=get_next_sector(&root))>=0) {
			while ( offset_in_current_root_sector + DIR_ENTRY_SIZE < SECTOR_SIZE) {
				int name = tolong(current_root_sector+offset_in_current_root_sector+4);
				int fb_file = tolong(current_root_sector+offset_in_current_root_sector+8) * SECTOR_SIZE;
				int current_sector;
				if (name > 0 && fb_file >=0 && fb_file < DISK_SIZE) {
					char fname[24];
					FILE * out;
					init_fb(&current, fb_file);
					snprintf(fname,23,"%d", name);
					if (write_files && !(out = fopen(fname,"wb"))) {
						write_error();
					}
					while ((current_sector=get_next_sector(&current))>=0)
						if (write_files && fwrite(&disk[current_sector],1,SECTOR_SIZE,out)!=SECTOR_SIZE)
							write_error();
					if (write_files && fclose(out)) write_error();
				}
				offset_in_current_root_sector += DIR_ENTRY_SIZE;
			}
			offset_in_current_root_sector = 0;
		}
	}
}
