/*
    CDAudioList Version 1.00, 01/09/2000

    Shell command to list tracks and CDID found on an audio CD.

    Based on Piotr Gapinski (narg@polbox.com) work (acdb,
    written in E) and on the RKRM SCSI_Direct.c example program.

    Adapted to C by Roland Florac (roland.florac@fnac.net)

    To compile with SAS/C (5056 bytes):
	sc LINK OPT PARM=REGS CDAudioList.c

    Installation:
	Just copy the CDAudioList program in your c: directory.

    Usage:
	CDAudioList unit [xxx.device]
	    scsi.device is used by default

    Examples:
	CDAudioList 3 atapi.device
	CDAudioList 3 scsi.device
	CDAudioList 1
*/

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

#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/devices.h>
#include <exec/io.h>
#include <devices/scsidisk.h>
#include <exec/nodes.h>
#include <dos/dos.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/alib_protos.h>

char version[] = "$VER: CDAudioList 1.00 (01/09/2000)";

struct scsicmd10 {
    char opcode;
    char b1, b2, b3, b4, b5, b6, b7, b8;
    char control;
};

struct msf {
    long min, sec, frm;
};

struct cdinfo {
    struct msf cdtoc[100];
    char idname[30];
    long tracks, time, cddb;
};

#define SCSI_CMD_READTOC 0x43
#define SENSE_LEN 252
#define MAX_TOC_LEN 804

char scsidata[MAX_TOC_LEN + SENSE_LEN], * tocbuffer;
struct MsgPort port;
struct IOStdReq scsio;


static void message (char * m)
{
    Write (Output(), m, strlen(m));
}

static long CDSetup (char * device, long unit)
{
    long error;

    tocbuffer = scsidata + MAX_TOC_LEN;

    port.mp_Node.ln_Pri = 0;			    /* setup the ReplyPort */
    port.mp_SigBit = AllocSignal (-1);
    port.mp_SigTask = (struct Task *) FindTask (0);
    NewList (&(port.mp_MsgList));
    scsio.io_Message.mn_ReplyPort = &port;

    error = OpenDevice (device, unit, &scsio, 0);
    if (error)
    {
	message ("Unable to open specified device\n");
	return FALSE;
    }
    return TRUE;
}

static void CDCleanUp (void)
{
    struct Message * msg;

    if (scsio.io_Device)
    {
	CloseDevice (&scsio);
	Forbid();
	while (msg = GetMsg (&port))
	    ReplyMsg (msg);
	Permit();
    }
}

static long cddbsum (long n)
{
    long ret = 0;

    while (n > 0)
    {
	ret = (ret + n % 10);
	n = (n / 10);
    }
    return ret;
}

static long cddb (long trks, struct msf * cdtoc)
{
    long i = 0, t, n = 0;

    while (i < trks)
    {
	n = n + cddbsum(((cdtoc[i].min) * 60) + (cdtoc[i].sec));
	i++;
    }
    n = (n % 255) << 24;
    t = (((cdtoc[trks].min)*60)+(cdtoc[trks].sec)) - (((cdtoc[0].min)*60)+(cdtoc[0].sec));
    t = t << 8;
    return (n | t | trks);
}

static BYTE doscsicmd (char cmd)
{
    struct SCSICmd scsicmd;
    struct scsicmd10 command = { SCSI_CMD_READTOC, 0, 0, 0, 0, 0, 0, 3, 0x24, 0 };

    command.b1 = cmd;
    scsio.io_Length = sizeof (scsicmd);
    scsio.io_Data = &scsicmd;
    scsio.io_Command = HD_SCSICMD;

    scsicmd.scsi_Data = (unsigned short *) tocbuffer;
    scsicmd.scsi_Length = MAX_TOC_LEN;
    scsicmd.scsi_SenseActual = 0;
    scsicmd.scsi_SenseLength = SENSE_LEN;
    scsicmd.scsi_SenseData = scsidata;
    scsicmd.scsi_Command = (unsigned char *) &command;
    scsicmd.scsi_CmdLength = sizeof (struct scsicmd10);
    scsicmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
    scsicmd.scsi_Status = 0;

    DoIO (&scsio);
    return scsio.io_Error;
}

static long fileid (char * cdid)
{
    long rc, tocsize, tocnumtracks;
    char * tocptr;
    long toctrackaddr[256];

    rc = doscsicmd (0);
    if (rc == 0)
    {
	tocsize = (tocbuffer[0] << 8) + tocbuffer[1];
	if (tocsize >= 2) tocsize = tocsize - 2;
	tocptr = tocbuffer + 4;
	tocnumtracks = 0;

	while (tocptr < (tocbuffer + 4 + tocsize))
	{
	    toctrackaddr[tocnumtracks] = ((tocptr[4] << 24) | (tocptr[5] << 16) | (tocptr[6] << 8) | (tocptr[7]));
	    tocnumtracks++;
	    tocptr = tocptr + 8;
	}
	tocnumtracks--;
	sprintf (cdid, "CDID %02ld%06lx%06lx\n", tocnumtracks, toctrackaddr[2], toctrackaddr[tocnumtracks], 0);
    }
    return rc;
}

static long readtocmsf (struct msf * msf)
{
    long tocsize;
    char * tocptr;
    long tocnumtracks = 0;

    if (doscsicmd (2) == 0)
    {
	tocsize = (tocbuffer[0] << 8) + tocbuffer[1];
	if (tocsize >= 2)
	    tocsize = tocsize - 2;
	tocptr = tocbuffer + 4;
	tocnumtracks = 0;

	while (tocptr < (tocbuffer + 4 + tocsize))
	{
	    msf[tocnumtracks].min = tocptr[5];
	    msf[tocnumtracks].sec = tocptr[6];
	    msf[tocnumtracks].frm = ((tocptr[5]*60)+(tocptr[6]))*75+tocptr[7];
	    tocptr = tocptr + 8;
	    tocnumtracks++;
	}
	tocnumtracks--;
    }
    return tocnumtracks;
}

static long CDCreateInfo (struct cdinfo * cd)
{
    cd->tracks = readtocmsf (&cd->cdtoc[0]);
    if (cd->tracks == 0)
    {
	message ("Cannot read TOC!\n");
	return 0;
    }
    else
    {
	cd->cddb = cddb (cd->tracks, cd->cdtoc);

	cd->time = (
	    ((cd->cdtoc[cd->tracks].min * 60) + cd->cdtoc[cd->tracks].sec) -
	    ((cd->cdtoc[0].min * 60) + cd->cdtoc[0].sec)
	);
	if (fileid (cd->idname))
	{
	    message ("Cannot compute CDID file name!\n");
	    return 0;
	}
    }
    return 1;
}

static long CDTrackTime (struct cdinfo * cd, long track, struct msf * msf)
{
    long time;

    if (track >= cd->tracks)
	return FALSE;
    time = ((cd->cdtoc[track + 1].frm) - (cd->cdtoc[track].frm))/75;
    msf->min = time / 60;
    msf->sec = time - msf->min * 60;
    return time;
}

static long CDTime (struct cdinfo * cd, struct msf * m)
{
    m->min = cd->time / 60;
    m->sec = cd->time - m->min * 60;
    return cd->time;
}

void main (int argc, char **argv)
{
    struct cdinfo cd;
    long i, unit;
    struct msf time;
    char * dname = "scsi.device", m[50];

    if ((argc < 2) || (argc > 3))
    {
	sprintf (m, "Usage: %s SCSIunit# [xxxx.device]\n",argv[0]);
	message (m);
    }
    else
    {
	StrToLong (argv[1], &unit);
	if (argc == 3)
	    dname = argv[2];

	if (CDSetup (dname, unit))
	{
	    if (CDCreateInfo (&cd))
	    {
		CDTime (&cd, &time);
		sprintf (m, "%ld tracks, total time %ld:%0.2ld\n", cd.tracks, time.min, time.sec);
		message (m);
		for (i = 1;  i <= cd.tracks;  i++)
		{
		    CDTrackTime (&cd, i - 1, &time);
		    sprintf (m, "Track %0.2ld: %ld:%0.2ld\n", i, time.min, time.sec);
		    message (m);
		}
		message (cd.idname);
	    }
	    CDCleanUp ();
	}
    }
}
