/*
 *  PSX-Tools Bundle Pack
 *  Copyright (C) 2002 Nicolas "Pixel" Noble
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "cdutils.h"
#include "fileutils.h"
#include "generic.h"

FILE * ppf_file = 0;
int pt1 = -1, pt2 = -1, snum = 0, ptl = 0, root = 0;

long sec_sizes[5] =  {0, 2048, 2336, 2048, 2324};
long sec_offsts[5] = {0,   16,   16,   24,   24};
char * sec_modes[5] = {"MODE 0 (no mode)", "MODE 1", "MODE 2", "MODE 2 FORM 1", "MODE 2 FORM 2"};

struct DirEntry rootDir;

unsigned char from_BCD(unsigned char x) {
    return ((x & 0xf) + ((x & 0xf0) >> 4) * 10);
}

unsigned char to_BCD(unsigned char x) {
    return ((x / 10) << 4) | (x % 10);
}

int is_valid_BCD(unsigned char x) {
    return (((x & 15) < 10) && ((x >> 4) < 10));
}

unsigned long from_MSF(unsigned char m, unsigned char s, unsigned char f, unsigned long start) {
    return (from_BCD(m) * 60 + from_BCD(s)) * 75 + from_BCD(f) - start;
}

unsigned long from_MSF(unsigned long msf, unsigned long start) {
    unsigned char
    f = msf & 0xff,
    s = (msf >> 8) & 0xff,
    m = (msf >> 16) & 0xff;
    
    return from_MSF(m, s, f, start);
}

FILE * open_ppf(char * ppf, FILE * iso, char * comment) {
    int i, l;
    char buffer[1024];

    if (!(ppf_file = fopen(ppf, "w")))
	return ppf_file;
    fwrite("PPF20\001", 1, 6, ppf_file);
    
    l = strlen(comment);
    if (l >= 50) {
	fwrite(comment, 1, 50, ppf_file);
    } else {
	char * t = " ";
	fwrite(comment, 1, l, ppf_file);
	for (i = l; i < 50; i++) {
	    fwrite(t, 1, 1, ppf_file);
	}
    }
    
    l = filesize(iso);
    fwrite(&l, 1, 4, ppf_file);
    
    fseek(iso, 0x9320, SEEK_SET);
    fread(buffer, 1, 1024, iso);
    fwrite(buffer, 1, 1024, ppf_file);
    return ppf_file;
}

void write_ppf(unsigned char * old_sec, unsigned char * new_sec, int sec_num) {
    int i, l = 0, o;
    
    for (i = 0; i < 2352; i++) {
	if (old_sec[i] == new_sec[i]) {
	    if (l != 0) {
		o = 2352 * sec_num + i;
		fwrite(&o, 1, 4, ppf_file);
		fwrite(&l, 1, 1, ppf_file);
		fwrite(new_sec + i - l - 1, 1, l, ppf_file);
		l = 0;
	    }
	} else {
	    l++;
	    if (l == 255) {
		o = 2352 * sec_num + i;
		fwrite(&o, 1, 4, ppf_file);
		fwrite(&l, 1, 1, ppf_file);
		fwrite(new_sec + i - 255, 1, 255, ppf_file);
		l = 0;
	    }
	}
    }
}

char * format_date(unsigned char * input) {
    static char output[26];
    
    memcpy(output, input + 6, 2);
    output[2] = '/';
    memcpy(output + 3, input + 4, 2);
    output[5] = '/';
    memcpy(output + 6, input, 4);
    output[10] = ' ';
    memcpy(output + 11, input + 8, 2);
    output[13] = ':';
    memcpy(output + 14, input + 10, 2);
    output[16] = ':';
    memcpy(output + 17, input + 12, 2);
    output[19] = '.';
    memcpy(output + 20, input + 14, 2);
    sprintf(output + 22, "%+3.1f", ((float) (*(((char *)input) + 16))) / 4);

    return output;
}

unsigned short int swap_word(unsigned short int i) {
    return (i >> 8) | (i << 8);
}

unsigned long int swap_dword(unsigned long int i) {
    return (i >> 24) | ((i >> 8) & 0x0000ff00) | ((i << 8) & 0x00ff0000) | (i << 24);
}

void sector_seek(FILE * f_iso, long sector) {
    long curpos;

    curpos = (2352 * (long) sector);
    fseek(f_iso, curpos, SEEK_SET);
}

int guess_type(FILE * f_iso, int number) {
    unsigned char header[24];
    
    if (number >= 0) {
	sector_seek(f_iso, number);
    }
    
    fread(header, 1, 24, f_iso);
    fseek(f_iso, -24, SEEK_CUR);
    if (header[15] == 1) {
	return MODE_1;
    } else if (header[15] == 2) {
	if (*((unsigned long *) &(header[16])) == *((unsigned long *) &(header[20]))) {
	    if ((header[16] == 0) && (header[17] == 0) && (header[19] == 0)) {
		if (header[18] & 0x20) {
		    return MODE_2_FORM_2;
		} else {
		    return MODE_2_FORM_1;
		}
	    }
	}
	return MODE_2;
    }
    
    return MODE_0;
}

long read_sector(FILE * f_iso, unsigned char * buffer, char type, int number) {
    if (number >= 0) {
	sector_seek(f_iso, number);
    }
    
    if (type == GUESS) {
	type = guess_type(f_iso, number);
    }
    
    fseek(f_iso, sec_offsts[type], SEEK_CUR);
    fread(buffer, 1, sec_sizes[type], f_iso);
    fseek(f_iso, 2352 - sec_offsts[type] - sec_sizes[type], SEEK_CUR);
    return sec_sizes[type];
}

void read_datas(FILE * f_iso, unsigned char * buffer, int type, int number, long size) {
    unsigned char sector[2352];
    int i, n, reste;
    
    if (type == GUESS) {
	type = guess_type(f_iso, number);
    }
    
    n = size / sec_sizes[type];
    reste = size - n * sec_sizes[type];
    
    sector_seek(f_iso, number);
    for (i = 0; i < n; i++) {
	read_sector(f_iso, buffer + i * sec_sizes[type], type);
    }
    
    if (reste) {
	read_sector(f_iso, sector, type);
	bcopy((char *) sector, (char *) (buffer + n * sec_sizes[type]), reste);
    }
}

void read_file(FILE * f_iso, FILE * file, char type, int number, long size) {
    unsigned char sector[2352];
    int i, n, reste;
    
    if (type == GUESS) {
	type = guess_type(f_iso, number);
    }
    
    n = size / sec_sizes[type];
    reste = size - n * sec_sizes[type];
    
    sector_seek(f_iso, number);
    for (i = 0; i < n; i++) {
	read_sector(f_iso, sector, type);
	fwrite(sector, 1, sec_sizes[type], file);
    }
    
    if (reste) {
	read_sector(f_iso, sector, type);
	fwrite(sector, 1, reste, file);
    }
}

void write_sector(FILE * f_iso_r, FILE * f_iso_w, unsigned char * buffer, char type, int number) {
    unsigned char old_sector[2352], new_sector[2352];

    if (type == GUESS) {
	type = guess_type(f_iso_r, number);
    }

    if (number >= 0) {
	sector_seek(f_iso_r, number);
	sector_seek(f_iso_w, number);
    }
    
    fread(old_sector, 1, 2352, f_iso_r);
    
    minute = old_sector[12];
    second = old_sector[13];
    frame = old_sector[14];
    
    bcopy((char *) old_sector, (char *) new_sector, 2532);
    bcopy((char *) buffer, (char *) new_sector + sec_offsts[type], sec_sizes[type]);
    
    do_encode_L2(new_sector, type, 0);
    if (ppf_file < 0) {
	fwrite(new_sector, 1, 2352, f_iso_w);
    } else {
	write_ppf(old_sector, new_sector, number);
    }
}

void write_datas(FILE * f_iso_r, FILE * f_iso_w, unsigned char * buffer, char type, int number, long size) {
    long nbsectors, i;
    unsigned char sector[2352];

    if (type == GUESS) {
	type = guess_type(f_iso_r, number);
    }

    if (number >= 0) {
	sector_seek(f_iso_r, number);
	sector_seek(f_iso_w, number);
    }
    
    nbsectors = size / sec_sizes[type];
    
    for (i = 0; i < nbsectors; i++) {
	write_sector(f_iso_r, f_iso_w, buffer + i * sec_sizes[type], type, number + i);
    }

    if (size % sec_sizes[type]) {
	memset(sector, 0, 2352);
	bcopy((char *) (buffer + i * sec_sizes[type]), (char *) sector, size % sec_sizes[type]);
	write_sector(f_iso_r, f_iso_w, sector, type, number + i);
    }
}

void write_file(FILE * f_iso_r, FILE * f_iso_w, FILE * file, char type, int number) {
    long size, nbsectors, i;
    unsigned char buffer[2352];
    
    if (type == GUESS) {
	type = guess_type(f_iso_r, number);
    }

    if (number >= 0) {
	sector_seek(f_iso_r, number);
	sector_seek(f_iso_w, number);
    }
    
    size = filesize(file);
    nbsectors = size / sec_sizes[type];
    
    if (size % sec_sizes[type]) {
	nbsectors++;
    }
    
    for (i = 0; i < nbsectors; i++) {
	memset(buffer, 0, 2352);
	fread(buffer, 1, sec_sizes[type], file);
	write_sector(f_iso_r, f_iso_w, buffer, type);
    }
}

void show_head_entry(void) {
    printm(M_BARE, "Sector -   Size   -   Date   -     Time     -   Flags  - Name\n");
}

int show_entry(struct DirEntry * dir) {
    char pbuf[200];
    if (!dir->R) {
	return 1;
    }
    
    strncpy(pbuf, (char *) &(dir->id), dir->N);
    pbuf[dir->N] = 0;

    printm(M_BARE, "%6i - %8i - %2i/%02i/%02i - %2i:%02i:%02i%+03.1f - %c%c%c%c%c%c%c%c - %s\n",
           dir->Sector, dir->Size, dir->Day, dir->Month, dir->Year, dir->Hour, dir->Minute, dir->Second, ((float) dir->Offset) / 4,
           dir->Flags & 1 ? 'H' : '-', dir->Flags & 2 ? 'D' : '-', dir->Flags & 4 ? 'A' : '-', dir->Flags & 8 ? 'R' : '-',
	   dir->Flags & 16 ? 'P' : '-', dir->Flags & 32 ? '1' : '-', dir->Flags & 64 ? '1' : '-', dir->Flags & 128 ? 'C' : '-', pbuf);
    return dir->R;
}

int show_dir(FILE * f_iso, struct DirEntry * dir) {
    unsigned int ptr;
    unsigned char * buffer;
    
    if (!(dir->Flags & 2)) {
	return 0;
    }
    
    buffer = (unsigned char *) malloc(dir->Size);
    read_datas(f_iso, buffer, GUESS, dir->Sector, dir->Size);
    
    ptr = 0;
    while(ptr < dir->Size) {
	ptr += show_entry((struct DirEntry *) &(buffer[ptr]));
    }
    
    free(buffer);
    return 1;
}

struct DirEntry find_dir_entry(FILE * f_iso, struct DirEntry * dir, char * name) {
    unsigned int ptr, size;
    unsigned char * buffer;
    struct DirEntry r = {0, 0, 0, 0, 0};
    
    if (!(dir->Flags & 2)) {
	return r;
    }
    
    buffer = (unsigned char *) malloc(size = dir->Size);
    read_datas(f_iso, buffer, GUESS, dir->Sector, dir->Size);
    
    ptr = 0;
    while(ptr < size) {
	dir = (struct DirEntry *) &(buffer[ptr]);
	if (!dir->R) {
	    ptr++;
	} else {
	    if (!strncmp(name, (char *) &(dir->id), dir->N)) {
		r = *dir;
	    }
	    ptr += dir->R;
	}
    }

    free(buffer);
    return r;
}

struct DirEntry * find_dir_entry(FILE * f_iso, unsigned char ** bufout, struct DirEntry * dir, char * name) {
    unsigned int ptr, size;
    unsigned char * buffer;
    struct DirEntry * rdir = 0;
    *bufout = 0;
    
    if (!(dir->Flags & 2)) {
	return 0;
    }
    
    buffer = (unsigned char *) malloc(size = dir->Size);
    read_datas(f_iso, buffer, GUESS, dir->Sector, dir->Size);
    
    ptr = 0;
    while(ptr < size) {
	dir = (struct DirEntry *) &(buffer[ptr]);
	if (!dir->R) {
	    ptr++;
	} else {
	    if (!strncmp(name, (char *) &(dir->id), dir->N)) {
		rdir = dir;
	    }
	    ptr += dir->R;
	}
    }

    if (rdir->R) {
	*bufout = buffer;
    } else {
	free(buffer);
    }
    return rdir;
}

int show_iso_infos(FILE * f_iso) {
    unsigned char buffer[2048];
    char pbuff[130];
    short int s, nogood = 0;

    read_sector(f_iso, buffer, GUESS, 16);

    printm(M_BARE, "Sector guessed mode : %s\n", sec_modes[guess_type(f_iso, 16)]);
    printm(M_BARE, "offset-size-info    : contents\n");
    printm(M_BARE, "    0 -   1-  1     : %i\n", buffer[0]);
    memcpy(pbuff, buffer + 1, 5);
    pbuff[5] = 0;
    printm(M_BARE, "    1 -   5-`CD001' : %s\n", pbuff);
    printm(M_BARE, "    6 -   2-  1     : %i\n", s = *((short int *) &(buffer[6])));
    printm(M_BARE, "(*this was the signature*)\n");
    if (buffer[0] != 1) nogood = 1;
    if (strcmp(pbuff, "CD001")) nogood = 1;
    if (s != 1) nogood = 1;
    
    if (nogood) {
	printm(M_BARE, "Not a valid iso9660 file.\n");
	return 0;
    }
    
    memcpy(pbuff, buffer + 8, 32);
    pbuff[32] = 0;
    printm(M_BARE, "    8 -  32- SYSID  : %s\n", pbuff);
    memcpy(pbuff, buffer + 40, 32);
    pbuff[32] = 0;
    printm(M_BARE, "   40 -  32- VOLID  : %s\n", pbuff);
    printm(M_BARE, "   80 -   8- SNum   : %li\n", *((long int *) &(buffer[80])));
    printm(M_BARE, "  120 -   4- VOLSiz : %i\n", *((short int *) &(buffer[120])));
    printm(M_BARE, "  124 -   4- VOLNum : %i\n", *((short int *) &(buffer[124])));
    printm(M_BARE, "  128 -   4- SSize  : %i\n", *((short int *) &(buffer[128])));
    printm(M_BARE, "  132 -   8- PSize  : %li\n", *((long int *) &(buffer[132])));
    printm(M_BARE, "  140 -   4- 1SLPath: %i\n", *((short int *) &(buffer[140])));
    printm(M_BARE, "  144 -   4- 2SLPath: %i\n", *((short int *) &(buffer[144])));
    printm(M_BARE, "  148 -   4- 1SBPath: %i\n", swap_dword(*((short int *) &(buffer[148]))));
    printm(M_BARE, "  152 -   4- 2SBPath: %i\n", swap_dword(*((short int *) &(buffer[152]))));
    memcpy(pbuff, buffer + 190, 128);
    pbuff[128] = 0;
    printm(M_BARE, "  190 - 128- VStId  : %s\n", pbuff);
    memcpy(pbuff, buffer + 318, 128);
    pbuff[128] = 0;
    printm(M_BARE, "  318 - 128- PubId  : %s\n", pbuff);
    memcpy(pbuff, buffer + 446, 128);
    pbuff[128] = 0;
    printm(M_BARE, "  446 - 128- DPrId  : %s\n", pbuff);
    memcpy(pbuff, buffer + 574, 128);
    pbuff[128] = 0;
    printm(M_BARE, "  574 - 128- AppId  : %s\n", pbuff);
    memcpy(pbuff, buffer + 702, 37);
    pbuff[37] = 0;
    printm(M_BARE, "  702 -  37- CpyFile: %s\n", pbuff);
    memcpy(pbuff, buffer + 739, 37);
    pbuff[37] = 0;
    printm(M_BARE, "  739 -  37- AbsFile: %s\n", pbuff);
    memcpy(pbuff, buffer + 776, 37);
    pbuff[37] = 0;
    printm(M_BARE, "  776 -  37- BibFile: %s\n", pbuff);
    printm(M_BARE, "  813 -  17- DTCreat: %s\n", format_date(&buffer[813]));
    printm(M_BARE, "  830 -  17- DTModif: %s\n", format_date(&buffer[830]));
    printm(M_BARE, "  847 -  17- DTExpir: %s\n", format_date(&buffer[847]));
    printm(M_BARE, "  864 -  17- DTEffec: %s\n", format_date(&buffer[864]));
    
    printm(M_BARE, "Root record:\n");
    show_head_entry();
    show_entry((DirEntry *) &(buffer[156]));
    
    return 1;
}

int get_iso_infos(FILE * f_iso) {
    unsigned char buffer[2048];
    char pbuff[130];
    short int s, nogood = 0;

    read_sector(f_iso, buffer, GUESS, 16);

    memcpy(pbuff, buffer + 1, 5);
    pbuff[5] = 0;

    s = *((short int *) &(buffer[6]));
    if (buffer[0] != 1) nogood = 1;
    if (strcmp(pbuff, "CD001")) nogood = 1;
    if (s != 1) nogood = 1;
    
    if (nogood) {
	printm(M_ERROR, "Not a valid iso9660 file.\n");
	return 0;
    }
    
    pt1 = *((short int *) &(buffer[140]));
    pt2 = *((short int *) &(buffer[144]));
    snum = *((int *) &(buffer[80]));
    ptl = *((long int *) &(buffer[132]));
    rootDir = *((struct DirEntry *) &(buffer[156]));
    return 1;
}

int get_pt_infos(FILE * f_iso) {
    unsigned char * buffer;
    
    if ((pt1 <= 0) && (pt2 <= 0))
	if (!get_iso_infos(f_iso))
	    return 0;
	    
    if ((!pt1) & (!pt2)) {
	printm(M_ERROR, "No path table defined.\n");
	return 0;
    }
    
    buffer = (unsigned char *) malloc(ptl);
    read_datas(f_iso, buffer, GUESS, !pt1 ? pt2 : pt1, ptl);
    
    if (buffer[0] == 1)
	if (buffer[1] == 0)
	    if (buffer[6] == 1)
		if (buffer[7] == 0)
		    if (buffer[8] == 0)
			if (buffer[9] == 0)
			    root = *((unsigned long int *) &(buffer[2]));

    free(buffer);
    return root ? 1 : 0;
}

int show_pt_infos(FILE * f_iso) {
    unsigned char * buffer;
    char pbuf[100];
    int i, ptr;
    
    if ((pt1 <= 0) && (pt2 <= 0))
	if (!get_iso_infos(f_iso))
	    return 0;
	    
    if ((!pt1) & (!pt2)) {
	printm(M_ERROR, "No path table defined.\n");
	return 0;
    }
    
    buffer = (unsigned char *) malloc(ptl);
    read_datas(f_iso, buffer, GUESS, !pt1 ? pt2 : pt1, ptl);
    
    printm(M_BARE, "node^paren@sector : name\n");
    for (ptr = 0, i = 1; buffer[ptr]; ptr += ptr & 1, i++) {
	strncpy(pbuf, (char *) &(buffer[8 + ptr]), buffer[ptr]);
	pbuf[buffer[ptr]] = 0;
	printm(M_BARE, "%3i ^ %3i @ %6i: %s\n", i, *((unsigned short *) &(buffer[6 + ptr])), *((unsigned long *) &(buffer[2 + ptr])), pbuf);
	ptr += 8 + buffer[ptr];
    }

    free(buffer);
    return 1;
}

struct DirEntry find_path(FILE * f_iso, char * path) {
    char ** pts = split(path, '/');
    struct DirEntry dir = {0, 0, 0, 0, 0};
    
    if ((pt1 <= 0) && (pt2 <= 0))
	if (!get_iso_infos(f_iso))
	    return dir;
	    
    if ((!pt1) & (!pt2)) {
	printm(M_ERROR, "No path table defined.\n");
	return dir;
    }
    
    if (!**pts)
	pts++;

    for (dir = rootDir; *pts; pts++) {
	if (!strlen(*pts))
	    continue;
	dir = find_dir_entry(f_iso, &dir, *pts);
	if (!dir.R)
	    return dir;
    }
    
    return dir;
}

struct DirEntry find_parent(FILE * f_iso, char * path) {
    char ** pts, * p;
    struct DirEntry dir = {0, 0, 0, 0, 0};
    
    if ((p = strchr(path, '/'))) {
	*p = 0;
    }
    if (!*path) {
	return rootDir;
    }
    pts = split(path, '/');
    
    if ((pt1 <= 0) && (pt2 <= 0))
	if (!get_iso_infos(f_iso))
	    return dir;
	    
    if ((!pt1) & (!pt2)) {
	printm(M_ERROR, "No path table defined.\n");
	return dir;
    }
    
    if (!**pts)
	pts++;

    for (dir = rootDir; *pts; pts++) {
	if (!strlen(*pts))
	    continue;
	dir = find_dir_entry(f_iso, &dir, *pts);
	if (!dir.R)
	    return dir;
    }
    
    return dir;
}