// decaf 0.1
// read Cathy .caf files
// for little endian, 4 byte int CPUs

// by hcs

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

char * dirname[65535];
short dirparent[65535];

int readstring(FILE * infile, char * str) {
    char * strend = str;
    while (fread(strend,1,1,infile)==1 && *strend) strend++;
    return strend-str;
}

void recursepath(int dirnum) {
    if (dirnum > 0)
	recursepath(dirparent[dirnum]);
	printf("%s",dirname[dirnum]);
    
    if (dirnum > 0)
	printf("\\");
}

int main(int argc, char ** argv) {
    FILE * infile;
    char buf[16];
    char fsname[1024];
    char fslabel[1024];
    char strbuf[1024];
    char pathname[1024];
    int filecount;
    int dircount;
    int filesize;
    int filedate;
    short parentdir;
    int i;

    // options
    int verbose=0;
    int fullpath=0;
    int rootpath=0;
    int nameonline=0;
    int labelonline=0;
    int dates=0;
    int sizes=0;
    int dirasfile=0;

    for (i=0;i<65535;i++) {dirparent[i]=-1; dirname[i]=0;}

    if (argc != 2 && argc != 3) {fprintf(stderr,"decaf 0.1\nread Cathy .caf files\n\nusage: %s [options] file.caf\n\noptions:\n\tv: Verbose\n\tp: show full Path for each file\n\tr: show fs Root in path (implies p)\n\tn: include fs Name on each line\n\tl: include fs Label on each line\n\td: include file Date on each line\n\ts: include file Size on each line\n\ta: list directories As files\n\n",argv[0]); return 1;}

    if (argc==3) {
	for (i=0;i<strlen(argv[1]);i++) {
	    switch (argv[1][i]) {
		case 'v':
		    verbose=1;
		    break;
		case 'p':
		    fullpath=1;
		    break;
		case 'r':
		    rootpath=1;
		    fullpath=1;
		    break;
		case 'n':
		    nameonline=1;
		    break;
		case 'l':
		    labelonline=1;
		    break;
		case 'd':
		    dates=1;
		    break;
		case 's':
		    sizes=1;
		    break;
		case 'a':
		    dirasfile=1;
		    break;
		default:
		    break;
	    }
	}
    }

    infile = fopen(argv[argc-1],"rb");
    
    if (!infile) {fprintf(stderr,"failed to open %s\n\n",argv[argc-1]); return 1;}

    // 0x00-0x03: 0x27,0x06,0xa4,0xd0
    fread(buf,1,4,infile);

    // 0x04-0x05: 0x05,0x00
    fread(buf,1,2,infile);

    // 0x06-0x09: ??
    fread(buf,1,4,infile);

    // 0x0a...: path (null term string)
    readstring(infile,pathname);

    if (verbose) printf("path:\t%s\n",pathname);

    // ...: disc label (null term string)
    readstring(infile,fslabel);

    if (verbose) printf("disc label:\t%s\n",fslabel);

    // ...: disc name (null term string)
    readstring(infile,fsname);

    if (verbose) printf("disc name:\t%s\n",fsname);

    // 4 bytes: serial number?
    fread(buf,1,4,infile);

    if (verbose) printf("serial no:\t%08x\n",*((unsigned long*)buf));

    // ...: description (null term string)
    readstring(infile,strbuf);

    if (verbose) printf("description:\t%s\n",strbuf);

    // two zero bytes (empty strings?)

    fread(buf,1,2,infile);

    // 2 byte number

    fread(buf,1,2,infile);

    // 4 byte dir count

    fread(&dircount,1,4,infile);

    if (verbose) printf("directory count:\t%d\n",dircount);

    // 1 byte

    fread(buf,1,1,infile);

    // 4 byte file count

    fread(&filecount,1,4,infile);

    if (verbose) printf("file count:\t%d\n",filecount);

    // some entries for directories... not sure what they're used for (aid searching?)

    for (i=0;i<dircount;i++) {
	if (verbose) printf("skipping...");

    	fread(buf,1,4,infile); // 4 bytes

	if (verbose) printf("%08x",*((int*)buf));

	fread(buf,1,4,infile); // 4 byte number

	if (verbose) printf("=%08x",*((int*)buf));

	fread(buf,1,4,infile); // 4 byte number

	if (verbose) printf("=%08x\n",*((int*)buf));

    }

    // root directory

    if (rootpath)
       dirname[0] = pathname;
    else
	dirname[0] = "";

    dirparent[0] = 0;
	
    // file entries

    for (i=0;i<filecount;i++) {
	fread(&filedate,1,4,infile); // 4 byte date

	fread(&filesize,1,4,infile); // 4 byte file size ( < 0 is directory)

	fread(&parentdir,1,2,infile); // 2 bytes (parent directory)

	readstring(infile,strbuf);

	if (filesize < 0) {
	    // directory is not included in filecount
	    i--;

	    if (fullpath) {
    		dirname[-filesize] = malloc(strlen(strbuf)+1);
    		if (!dirname[-filesize]) {fprintf(stderr,"failed to allocate memory for directory names\n"); return 1;}
    		strcpy(dirname[-filesize],strbuf);
    		dirparent[-filesize]=parentdir;
	    }
	    if (dirasfile) {
		if (nameonline) printf("%s\t",fsname);
		if (labelonline) printf("%s\t",fslabel);
	        if (fullpath) recursepath(parentdir);
		printf("%s",strbuf);
		if (sizes) printf("\t0");
		if (dates) printf("\t%s",ctime(&filedate));
		else printf("\n");
	    }
	} else {
    	    if (nameonline) printf("%s\t",fsname);
	    if (labelonline) printf("%s\t",fslabel);
	    if (fullpath) recursepath(parentdir);
	    printf("%s",strbuf);
	    if (sizes) printf("\t%d",filesize);
	    if (dates) printf("\t%s",ctime(&filedate));
	    else printf("\n");
	}
    }

    fclose(infile);

    return 0;
}

