/********************************************

	CD Access Func

*********************************************/
#include    "defs.h"

#ifdef	MSC
#  define	HAVE_STRERROR
#endif

#ifdef	HAVE_STRERROR
#include    <errno.h>
#endif

	ulong	vtoc_start_block = 0L;
	ulong	vtoc_end_block = 0L;
	int	vtoc_file_max = 0;

	char	file_name[48];
	ulong	file_start_block = 0L;
	ulong	file_end_block = 0xFFFFFFFFL;

	uchar	dic_buf[BLOCK_SIZE];

#ifdef	MSCDEX
static	int	mscdex_unit_max = 0;
#endif

/*	compess dic	*/

	int	comp_dic_mode = FALSE;
static	ulong	comp_tab[256];
static	ulong	comp_seek = 0xFFFFFFFFL;

static	int	decode(char *buf, int len, FILE *fp)
{
    int     i, n, c, d, bit;

    n = 0;
    while ( n < len && (bit = getc(fp)) != EOF ) {
	for ( i = 1 ; i < 0x100 && n < len ; i <<= 1 ) {
	    if ( (bit & i) == 0 ) {
		if ( (c = getc(fp)) == EOF || (d = getc(fp)) == EOF )
		    break;
		d |= (c << 8);
		c = ((d & 0x1F) + 3);
		d >>= 5; d &= 0x7FF;
		while ( c-- > 0 && n < len )
		    buf[n++] = buf[d++];
	    } else {
		if ( (c = getc(fp)) == EOF )
		    break;
		buf[n++] = c;
	    }
	}
    }

    return n;
}
#ifdef	FMRBIOS
int	CD_mode_set(int mode)
{
    union REGS	 regs;

    regs.h.ah = 0x00;
    regs.h.al = dev_unit_no;
    regs.h.ch = 0x00;
    regs.h.dl = mode;
    int86(0x93, &regs, &regs);
    return (regs.h.ah);
}
#endif	/* FMRBIOS */

#ifdef	MSCDEX
int	CD_mscdex_init()
{
/*
** Get CD-ROM Drive Number
**  INT 2Fh FUNCTION 00h
**  return: cx = Drive Number (A=0, B=1...)
**          bx = Drive Count
*/
    union REGS inregs, outregs;
    static char tmp[8];

    inregs.h.ah = 0x15;			/* MSCDEX Function Call */
    inregs.h.al = 0x00;			/* MSCDEX Sub-Function */
    inregs.x.bx = 0;
    int86(0x2f, &inregs, &outregs);	/* MSCDEX Call */
    if (outregs.x.bx == 0)
	return ERR;			/* Not Install MSCDEX.EXE */

    mscdex_unit_max = outregs.x.bx;	/* Return CD-ROM Drive Count  */
    dev_unit_no     = outregs.x.cx;	/* Return CD-ROM Drive Number */

    sprintf(tmp, "%x", dev_unit_no);
    dev_unit_name   = tmp;

    return 0;
}
#endif	/* MSCDEX */

static	int	REAL_read_sect(ulong sect, int count, char *buff)
{
#ifdef	FMRBIOS
    union REGS	 regs;
    struct SREGS segs;
    char far     *p;

    if ( now_dic_mode == DIC_FILE_MODE )
	goto FILEIO;

    p = (char far *)buff;
    regs.h.ah = 0x05;
    regs.h.al = dev_unit_no;
    regs.h.ch = 0x00;
    regs.h.cl = (uchar)(sect >> 16);
    regs.x.dx = (ushort)sect;
    regs.x.bx = count;
    segs.ds   = FP_SEG(p);
    regs.x.di = FP_OFF(p);
    int86x(0x93, &regs, &regs, &segs);
    return (regs.h.ah);

#else
#ifdef	MSCDEX

    union REGS regs;
    struct SREGS segs;
    char far     *p;

    if ( now_dic_mode == DIC_FILE_MODE )
	goto FILEIO;

    p = (char far *)buff;
    regs.h.ah = 0x15;			/* MSCDEX Function Call */
    regs.h.al = 0x08;			/* MSCDEX Sub-Function */
    regs.x.cx = dev_unit_no;
    regs.x.si = (uchar)(sect >> 16);
    regs.x.di = (ushort)sect;
    regs.x.dx = count;
    regs.x.bx = FP_OFF(p);
    segs.es = FP_SEG(p);
    int86x(0x2f, &regs, &regs, &segs);	/* MSCDEX Call */
    if ((regs.x.ax & 0x00ff) != 0)
	return ERR;
    return 0;

#endif	/* MSCDEX */
#endif	/* FMRBIOS */

FILEIO:

    if ( comp_dic_mode ) {
	if ( comp_seek != (sect & 0xFFFFFF00L) ) {
	    comp_seek = (sect & 0xFFFFFF00L);
	    if ( fseek(dev_fp, comp_seek * sizeof(long) + 4L, 0) ||
		 fread(comp_tab, sizeof(long), 256, dev_fp) != 256 )
		goto ERROR;
	}

	if ( comp_tab[sect & 0xFF] == 0L ||
	     fseek(dev_fp, comp_tab[sect & 0xFF], 0) )
	    goto ERROR;

	while ( count-- > 0 ) {
	    if ( decode(buff, BLOCK_SIZE, dev_fp) != BLOCK_SIZE )
		goto ERROR;
	    buff += BLOCK_SIZE;
	}

    } else if ( fseek(dev_fp, sect * BLOCK_SIZE, 0) ||
	 	fread(buff, BLOCK_SIZE, count, dev_fp) != count )
	goto ERROR;

    return FALSE;

ERROR:

#ifdef	HAVE_STRERROR
    fprintf(stderr, "dic: %s\n", strerror(errno));
#else
    fprintf(stderr, "dic: Dictionary Read Error\n");
#endif
    return ERR;
}
/********************************************

	Disk Cache Func

*********************************************/
#ifdef	UNIX
#  define	CACHE_MAX	32
#else
#  define	CACHE_MAX	16
#endif

typedef	struct _CAHPTR {
	struct _CAHPTR	*next;
	int		id;
	ulong		block;
	char FAR	*buff;
} CAHPTR;

static	CAHPTR	*cache_top = NULL;
static	CAHPTR	cache_tab[CACHE_MAX];

#ifdef	MSDOS
static	char far *FAR_MEMCPY(char far *buf, register char *s, int len)
{
    register char far *p = buf;

    while ( len-- > 0 )
	*(p++) = *(s++);
    return buf;
}
static	char *NEAR_MEMCPY(char *buf, register char far *s, int len)
{
    register char *p = buf;

    while ( len-- > 0 )
	*(p++) = *(s++);
    return buf;
}
#endif	/* MSDOS */

int	Cache_init()
{
    int     n;

    now_dev_id = (-1);
    comp_seek = 0xFFFFFFFFL;
    cache_top = NULL;
    for ( n = 0 ; n < CACHE_MAX ; n++ ) {
	if ( (cache_tab[n].buff = (char FAR *)MALLOC(BLOCK_SIZE)) == NULL )
	    break;
	cache_tab[n].next = cache_top;
	cache_top = &(cache_tab[n]);
	cache_tab[n].id    = (-1);
	cache_tab[n].block = 0xFFFFFFFFL;
    }
    return FALSE;
}
int	Cache_flush(int id)
{
    if ( now_dev_id == id )
	return TRUE;

    now_dev_id = id;
    comp_seek = 0xFFFFFFFFL;

    return FALSE;
}
int	CD_read_sect(ulong block, int count, char *buff)
{
    register CAHPTR *bp;
    CAHPTR *tp;

    while ( count-- > 0 ) {
	tp = bp = cache_top;
	for ( ; ; ) {
	    if ( bp->block == block && bp->id == now_dev_id ) {
		if ( tp != bp ) {
		    tp->next = bp->next;
		    bp->next = cache_top;
		    cache_top = bp;
		}
		NEAR_MEMCPY(buff, bp->buff, BLOCK_SIZE);
		break;
	    }
	    if ( bp->next == NULL ) {
		if ( REAL_read_sect(block, 1, buff) )
		    return ERR;
		tp->next = bp->next;
		bp->next = cache_top;
		cache_top = bp;
		bp->id    = now_dev_id;
		bp->block = block;
		FAR_MEMCPY(bp->buff, buff, BLOCK_SIZE);
		break;
	    }
	    tp = bp;
	    bp = bp->next;
	}

	block++;
	buff += BLOCK_SIZE;
    }
    return FALSE;
}
/********************************************

	VTOC File Access Func

*********************************************/
int	CD_read_time(int min, int sec, int blk, int count, char *buff)
{
    ulong sect;

    sect = min * (60L * 75L);
    sect += (sec * 75L);
    sect += blk;
    sect -= (2 * 75L);		/* 0 is 0:2:0 */

    return CD_read_sect(sect, count, buff);
}
static	char	*CD_form_name(uchar *ptn, int len)
{
    static char buf[128];
    static char ext[] = { 
	'\0', '?', '?', '?', '?', '?', '?', '?',
	'?', '?', '?', '\\', '?', '?', '?', '?'
    };
    int     n, ch;

    for ( n = 0 ; n < len && n < 127 ; n++ ) {
	switch(ptn[n] & 0xF0) {
	case 0x40:
	    ch = ext[ptn[n] & 0x0F];
	    break;
	case 0xC0:
	    ch = '@' + (ptn[n] & 0x0F);
	    break;
	case 0xD0:
	    ch = 'I' + (ptn[n] & 0x0F);
	    break;
	case 0xE0:
	    ch = 'Q' + (ptn[n] & 0x0F);
	    break;
	case 0xF0:
	    ch = '0' + (ptn[n] & 0x0F);
	    break;
	}
	buf[n] = ch;
	if ( ch == 0 )
	    break;
    }

    buf[n] = '\0';
    return buf;
}
static	ulong	CD_time_to_sect(int min, int sec, int blk)
{
    return ((ulong)min * 60L * 75L + sec * 75L + blk - (2 * 75L));
}
int	CD_file_init()
{
    int    min, sec, blk;

#ifdef	FMRBIOS
    if ( towns_flg && CD_mode_set(0x04) )
	return ERR;
#endif

    if ( CD_read_time(0, 3, 38, 1, dic_buf) )
        return ERR;

    if ( strcmp(CD_form_name(dic_buf, 3), "VTC") != 0 )
        return ERR;

    min = (dic_buf[180] & 0x0F) * 10 + (dic_buf[181] & 0x0F);
    sec = (dic_buf[182] & 0x0F) * 10 + (dic_buf[183] & 0x0F);
    blk = (dic_buf[184] & 0x0F) * 10 + (dic_buf[185] & 0x0F);
    vtoc_start_block = CD_time_to_sect(min, sec, blk);

    min = (dic_buf[188] & 0x0F) * 10 + (dic_buf[189] & 0x0F);
    sec = (dic_buf[190] & 0x0F) * 10 + (dic_buf[191] & 0x0F);
    blk = (dic_buf[192] & 0x0F) * 10 + (dic_buf[193] & 0x0F);
    vtoc_end_block = CD_time_to_sect(min, sec, blk);

    vtoc_file_max = vtoc_end_block - vtoc_start_block + 1;

    return FALSE;
}
int	CD_file_open(int no)
{
    int    min, sec, blk;
    ulong  sect;

    sect = vtoc_start_block + no - 1;

    if ( sect < vtoc_start_block || sect > vtoc_end_block )
	return ERR;

    if ( CD_read_sect(sect, 1, dic_buf) )
        return ERR;

    if ( strcmp(CD_form_name(dic_buf, 3), "HDR") != 0 )
        return ERR;

    strcpy(file_name, CD_form_name(dic_buf + 4, 47));

    min = (dic_buf[180] & 0x0F) * 10 + (dic_buf[181] & 0x0F);
    sec = (dic_buf[182] & 0x0F) * 10 + (dic_buf[183] & 0x0F);
    blk = (dic_buf[184] & 0x0F) * 10 + (dic_buf[185] & 0x0F);
    file_start_block = CD_time_to_sect(min, sec, blk);

    min = (dic_buf[188] & 0x0F) * 10 + (dic_buf[189] & 0x0F);
    sec = (dic_buf[190] & 0x0F) * 10 + (dic_buf[191] & 0x0F);
    blk = (dic_buf[192] & 0x0F) * 10 + (dic_buf[193] & 0x0F);
    file_end_block = CD_time_to_sect(min, sec, blk);

    return FALSE;
}
/********************************************

	CD Low Block I/O Func

*********************************************/

static	ulong	io_block;
static	ushort	io_offset;

int	IO_block_read(ulong block)
{
    block = file_start_block + block - 1;

    if ( block < file_start_block || block > file_end_block )
	return ERR;

    return CD_read_sect(block, 1, dic_buf);
}
int	IO_seek(ulong block, ushort offset)
{
    io_block  = block;
    io_offset = offset;
    return IO_block_read(io_block++);
}
int	IO_getc()
{
    if ( io_offset >= BLOCK_SIZE ) {
	if ( IO_block_read(io_block++) )
	    return EOF;
	io_offset = 0;
    }
    return (dic_buf[io_offset++] & 0xFF);
}
int	IO_read(char *buf, int size)
{
    int     ch, len;

    for ( len = 0 ; len < size ; ) {
	if ( (ch = IO_getc()) == EOF )
	    break;
	buf[len++] = ch;
    }
    return len;
}
/********************************************

	Device/File Name ID Make/Get

*********************************************/
typedef	struct _IP {
	struct _IP	*next;
	int		id;
	char		name[1];
} NAMEID;

static	int	id_counter = 0;
static	NAMEID	*name_id_ptr = NULL;

char	*id_to_name(int id)
{
    NAMEID *np;

    np = name_id_ptr;
    while ( np != NULL ) {
	if ( np->id == id )
	    return np->name;
	np = np->next;
    }
    return NULL;
}
int	name_to_id(char *name)
{
    NAMEID *np;

    np = name_id_ptr;
    while ( np != NULL ) {
	if ( strcmp(np->name, name) == 0 )
	    return np->id;
	np = np->next;
    }

    if ( (np = (NAMEID *)malloc(sizeof(NAMEID) + strlen(name))) == NULL ) {
	fprintf(stderr, "dic: Name ID struct memory alloc error\n");
	exit(1);
    }

    np->next = name_id_ptr;
    name_id_ptr = np;
    np->id = id_counter++;
    strcpy(np->name, name);

    return np->id;
}
