/*****************************************************************************
**
**  Copyright 1998 by Ernest R. Schreurs.
**  All rights reserved.
**  Use of this source code is allowed under the following conditions:
**  If you change anything, use a different program name.
**  If you only port this code, you can use this program name.
**  Apart from this, the source code is public domain.
**  This program is totally free.
**  Refer to the documentation for more information.
**
*****************************************************************************/
#define MODULE  "DCM2ATR.C"
/*****************************************************************************
**  NAME: DCM2ATR.C
**
**  Author            : Ernest R. Schreurs
**  Date              : February 2, 1998
**  Release           : 01.01
**
**  Description       : This program will convert a Diskcomm file to a SIO2PC
**                      disk image file, also known as ATR file.
**
*****************************************************************************/

/*****************************************************************************
==  INCLUDE FILES
*****************************************************************************/
#include <stdio.h>              /* For printf() and gets()              */
#include <ctype.h>              /* For isalnum() and toupper()          */
#include <stdlib.h>             /* For the exit function                */
#include <string.h>             /* String and memory stuff              */

/*****************************************************************************
==  DEFINED SYMBOLS
*****************************************************************************/

#ifndef FALSE
#define FALSE               0
#endif
#ifndef TRUE
#define TRUE                1
#endif

#ifndef NULL
#define NULL                0
#endif
#define PATH_LEN            128             /* Maximum path length          */

#define BUF_LEN             80              /* fgets buffer length          */
#define SUCCESS             1               /* Success is non-zero          */
#define FAILURE             0               /* Failure is zero              */

/*
**  These are the compression types used to indicate the type of record.
*/
#define DCM_CHANGE_BEGIN    0x41            /* Change only start of sector  */
#define DCM_DOS_SECTOR      0x42            /* 128 byte compressed sector   */
#define DCM_COMPRESSED      0x43            /* Uncompressed/compressed pairs*/
#define DCM_CHANGE_END      0x44            /* Change only end of sector    */
#define DCM_FLUSH_BUFFER    0x45            /* Flush the buffer             */
#define DCM_SAME_AS_BEFORE  0x46            /* Same as previous non-zero    */
#define DCM_UNCOMPRESSED    0x47            /* Uncompressed sector          */

/*
**  These are the density types.
*/
#define DENSITY_SD          1               /* Single density, 90K          */
#define DENSITY_DD          2               /* Double density, 180K         */
#define DENSITY_ED          3               /* Enhanced density, 130K       */

/*****************************************************************************
==  MACRO DEFINITIONS
*****************************************************************************/
/*
**  Macro for casting stuff to requirements of stupid
**  standard library functions.
*/

#define FGETS( buf, buf_len, file_ptr )                                 \
    (void *)fgets( (char *)buf, (int)buf_len, (FILE *)file_ptr )

#define STRLEN( str )                                                   \
    strlen( (const char *)(str) )

/*
**  Macro for getting input from the terminal,
**  allowing the user to exit with either control Z or
**  inputting the string ^Z to indicate intention of
**  terminating the program.
*/
#define GET_BUF()                                                           \
{                                                                           \
    if ( FGETS( buf, BUF_LEN, stdin )  ==  NULL )                           \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        panic(0);                                                           \
    }                                                                       \
    if ( memcmp( buf, "^Z", 2 ) == 0 || memcmp( buf, "^z", 2 ) == 0  )      \
    {                                                                       \
        printf( "Terminated by ^Z\n" );                                     \
        panic(0);                                                           \
    }                                                                       \
}

#define PRINT( lst )                                                        \
{                                                                           \
    if( diagnostics )                                                       \
    {                                                                       \
        printf lst;                                                         \
    }                                                                       \
}

/*****************************************************************************
==  TYPE and STRUCTURE DEFINITIONS
*****************************************************************************/
typedef     unsigned char   bool;   /* Boolean value                        */
typedef     unsigned char   ubyte;  /* Exactly eight bits, unsigned         */
typedef     short           int16;  /* At least 16 bits, signed             */
typedef     unsigned short  uint16; /* At least 16 bits, unsigned           */
typedef     long            int32;  /* At least 32 bits, signed             */
typedef     unsigned long   uint32; /* At least 32 bits, unsigned           */

/*
**  SIO2PC image file header.
*/
typedef struct
{
    ubyte       atr_hdr_lo;             /* Header, sum of NICKATARI, low    */
    ubyte       atr_hdr_hi;             /* and high bytes of the word       */
    ubyte       atr_siz_lo;             /* Size of atr excluding header,    */
    ubyte       atr_siz_hi;             /* expressed in paragraphs, low/high*/
    ubyte       atr_sec_size_lo;        /* Sector size, low byte            */
    ubyte       atr_sec_size_hi;        /* Sector size, high byte           */
    ubyte       atr_hi_size_lo;         /* High order portion of 32 bit size*/
    ubyte       atr_hi_size_hi;         /* of atr, low/high                 */
    ubyte       atr_d_info;             /* Copy protect/write protect flags */
    ubyte       atr_sec_lo;             /* Sector number of typical copy    */
    ubyte       atr_sec_hi;             /* protected sector, low/high       */
    ubyte       atr_tunused[5];         /* Unused spare bytes               */
} atr_blk;

/*****************************************************************************
==  IMPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) VARIABLES
*****************************************************************************/
static FILE *   atr_file;               /* SIO2PC disk image file           */
static FILE *   dcm_file;               /* Diskcomm input file              */
static ubyte    atr_path[PATH_LEN];     /* Output atr file spec             */
static ubyte    dcm_path[PATH_LEN];     /* Input dcm file spec              */

static ubyte    sec_buf[256];           /* The sector buffer                */
static ubyte    sec_zer[256];           /* A sector buffer with zeroes      */

static uint32   cnt_41;                 /* Number of 0x41 blocks            */
static uint32   cnt_42;                 /* Number of 0x42 blocks            */
static uint32   cnt_43;                 /* Number of 0x43 blocks            */
static uint32   cnt_44;                 /* Number of 0x44 blocks            */
static uint32   cnt_45;                 /* Number of 0x45 blocks            */
static uint32   cnt_46;                 /* Number of 0x46 blocks            */
static uint32   cnt_47;                 /* Number of 0x47 blocks            */
static uint32   cnt_F9;                 /* Number of 0xF9 headers           */
static uint32   cnt_FA;                 /* Number of 0xFA headers           */

static int      dcm_byte;               /* Byte from the Diskcomm file      */
static ubyte    density;                /* Density type                     */
static bool     diagnostics;            /* Print diagnostic data            */
static ubyte    file_type;              /* File type from header            */
static uint16   increment;              /* Byte of path to increment        */
static bool     large;                  /* Large file/small files           */
static bool     last;                   /* Last pass                        */
static uint16   max_sector;             /* Highest sector number            */
static ubyte    pass;                   /* Pass count                       */
static ubyte    pass_cnt;               /* Pass counter                     */
static ubyte    pass_expected;          /* Pass count we should be at       */
static uint32   sec_size;               /* Sector size                      */
static uint16   sector;                 /* Sector number                    */
static uint16   sector_atr;             /* Sector number written to atr file*/
static uint16   sectors;                /* Number of sectors on disk        */
static bool     seq_flag;               /* Sequential sector flag           */
static bool     statistics;             /* Print statistics information     */
static ubyte    type;                   /* Type of compression              */

/*****************************************************************************
==  EXPORTED VARIABLES
*****************************************************************************/
/*****************************************************************************
==  IMPORTED FUNCTIONS
*****************************************************************************/
/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/
static void     cleanup( void );
static uint32   dcm2atr( void );
static ubyte    get_byte( void );
static void     panic( int error_level );
static uint32   process_header( void );
static void     usage( char * cmd );

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/
int                 main();                 /* Normal entry point to it all */

/*****************************************************************************
==  LOCAL ( HIDDEN ) FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  cleanup()
**
**  PURPOSE:
**      Cleanup any mess that was created.
**
**  DESCRIPTION:
**      This function will attempt to close all open files and
**      free allocated memory.
**
**  INPUT:
**      - The file pointers and paths are used.
**
**  OUTPUT:
**      The function returns nothing.
**
*/

static void         cleanup( void )
{
    if( dcm_file )
    {
        fclose( dcm_file );
    }
    if( atr_file )
    {
        fclose( atr_file );
    }
    return;
}

/*****************************************************************************
**  NAME:  dcm2atr()
**
**  PURPOSE:
**      Read data from the .dcm file and convert it to sectors in the ATR file.
**
**  DESCRIPTION:
**      This function will read the data and process it.
**
**  INPUT:
**      Nothing.
**      Data is taken from the dcm file.
**
**  OUTPUT:
**      Writes the .atr file.
**      Prints results.
**      Returns SUCCESS if data converted successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       dcm2atr( void )
{
    uint16          bytes_done;             /* Number of bytes filled       */
    uint16          end_offset;             /* Offset of end of string      */
    ubyte           fill_char;              /* Fill character               */
    uint16          level;                  /* Interpreted level of signal  */
    uint16          offset;                 /* Offset into sector           */

/*
**  The sequential flag tells us whether or not the next sector data is
**  related to the next sector on the disk.  Sectors that are all zeroes are
**  not stored in the .dcm file, so this is indicated by a flag.  The next
**  sector number is then stored prior to that sector data.  Get the
**  sector number if the next sector is not the next sector in sequence.
*/
    do                          /* Until we find a flush buffer code */
    {
        if( seq_flag )
        {
            sector++;           /* Next sequential sector */
            PRINT( ("Sequential sector: %u\n", sector ) );
        }
        else
        {
            sector = get_byte();
            sector += ( ((uint16)get_byte()) << 8 );
            PRINT( ("Jump to sector: %u\n", sector ) );
        }

        type = get_byte();
        seq_flag = ( type & 0x080 ) ? TRUE : FALSE;
        type &= 0x7F;

        PRINT( ("Type: %.2x sector: %u\n", type, sector ) );

        switch( type )
        {
            case DCM_CHANGE_BEGIN:      /* 0x41 */
                cnt_41++;
                offset = get_byte();
                do                      /* Reverse copy to beginning    */
                {
                    sec_buf[offset] = get_byte();
                } while( offset-- );
                PRINT( ("Beginning of sector reached\n") );
                break;
            case DCM_DOS_SECTOR:        /* 0x42 */
                cnt_42++;
                for( offset = 123; offset < 128; offset++ )
                {
                    sec_buf[offset] = get_byte();
                }
                memset( sec_buf, sec_buf[123], 123 );
                break;
            case DCM_COMPRESSED:        /* 0x43 */
                cnt_43++;
                for( offset = 0, bytes_done = 0; bytes_done < sec_size; )
                {
                    end_offset = get_byte();
                    while( offset != end_offset )
                    {
                        sec_buf[offset] = get_byte();
                        offset = (++offset) & 0x0FF; /* Roll over 255 to 0 */
                        bytes_done++;
                        if( bytes_done == sec_size )
                            break;
                    } /* end while uncompressed string */
                    PRINT( ("Uncompressed end\n") );
                    if( bytes_done < sec_size )
                    {
                        end_offset = get_byte();
                        fill_char = get_byte();
                        do /* while end of compressed string not reached */
                        {
                            sec_buf[offset] = fill_char;
                            offset = (++offset) & 0x0FF;
                            bytes_done++;
                            if( bytes_done == sec_size )
                                break;
                        } while( offset != end_offset );
                        PRINT( ("Compressed end\n") );
                    } /* End if space left in sector */
                } /* end for uncompressed/compressed pairs */
                break;
            case DCM_CHANGE_END:        /* 0x44 */
                cnt_44++;
                offset = get_byte();
                do                      /* Copy bytes up to the end         */
                {
                    sec_buf[offset++] = get_byte();
                } while( offset < sec_size );
                PRINT( ("End of sector reached\n") );
                break;
            case DCM_FLUSH_BUFFER:      /* 0x45 */
                cnt_45++;
                break;
            case DCM_SAME_AS_BEFORE:    /* 0x46 */
                cnt_46++;
                break;
            case DCM_UNCOMPRESSED:      /* 0x47 */
                cnt_47++;
                for( offset = 0; offset < sec_size; offset++ )
                {
                    sec_buf[offset] = get_byte();
                }
                break;
            default:
                fprintf(stderr, "\n%s is not a valid Diskcomm archive,\n", dcm_path);
                fprintf(stderr, "or archive damaged, bad record type encountered.\n");
                if( !large )
                    printf("Did you forget the /B switch in the COPY command?\n");
                panic( 255 );
                break;
        }
        if( type != DCM_FLUSH_BUFFER )
        {

/*
**  Write the sector data to the atr file.  If we skipped sectors, we must
**  fill these sectors with zeroes, and write them to the file first.
**  Remember that the first three sectors are always 128 bytes.
*/
            for( sector_atr++; sector_atr < sector; sector_atr++ )
            {
                fwrite( sec_zer, 1, (sector_atr < 4) ? 128 : sec_size, atr_file );
            }
            fwrite( sec_buf, 1, (sector_atr < 4) ? 128 : sec_size, atr_file );
        }
    } while( type != DCM_FLUSH_BUFFER );
}

/*****************************************************************************
**  NAME:  get_byte()
**
**  PURPOSE:
**      Get one byte from the .dcm file.
**
**  DESCRIPTION:
**      This function will read one byte from the .dcm file, and return
**      it as an unsigned byte.
**
**  INPUT:
**      - The diskcomm file.
**
**  OUTPUT:
**      The function returns an unsigned byte.
**
*/

static ubyte        get_byte( void )
{

/*
**  Get the next byte from the Diskcomm file.
**  If we get an End Of File condition, this is an error, since we never
**  call this function unless we are sure we need more bytes.
*/
    dcm_byte = fgetc( dcm_file );

    if( dcm_byte == EOF )
    {
        fprintf(stderr, "\n%s is not a valid Diskcomm archive,\n", dcm_path);
        fprintf(stderr, "premature end of file encountered.\n");
        if( !large )
        {
            printf("Did you merge all parts of this Diskcomm archive with the COPY command?\n");
            printf("Did you forget the /B switch in the COPY command?\n");
            printf("Did you copy the files in the proper order?\n");
            printf("Did you read the documentation?\n");
        }
        panic( 255 );
    }

    PRINT( ("get_byte returns %.2x\n", dcm_byte ) );

    return( ((ubyte)(dcm_byte & 0x0FF)) );
}

/*****************************************************************************
**  NAME:  panic()
**
**  PURPOSE:
**      Abort all processing and terminate program.
**
**  DESCRIPTION:
**      This function will attempt to close all open files and
**      free allocated memory.  It will then terminate the program.
**
**  INPUT:
**      - The error level code to be handed to the Operating System.
**
**  OUTPUT:
**      The function returns nothing.  In fact, it does not return at all.
**
*/

static void         panic( error_level )
int                 error_level;            /* Error level value to pass    */
{
    cleanup();
    exit( error_level );
}

/*****************************************************************************
**  NAME:  process_header()
**
**  PURPOSE:
**      Process the data from the header of the .dcm file and store
**      relevant information.
**
**  DESCRIPTION:
**      This function will read the relevant data about the .dcm file
**      and store it.
**
**  INPUT:
**      Nothing.
**      Data is read from the Diskcomm file.
**
**  OUTPUT:
**      Prints results.
**      Stores data related to the format and contents of the Diskcomm file.
**      Returns SUCCESS if header was processed successfully.
**      Returns FAILURE if some error occurred.
**
*/

static uint32       process_header( void )
{

/*
**  Diskcomm files always start with either $FA or $F9.
**  Files that start with $F9 are multiple file archives.
**  Files that start with $FA are single file archives.
**  The second byte holds the following pieces of information:
**  The high order bit indicates whether or not this is the last pass.
**  The next two bits specifies the disk format of the original disk.
**  00 is single denisty, 01 is double density, 10 is enhanced density.
**  The last 5 bits indicate the pass count value.
**  After this, we always find a two-byte sector number,
**  stored in the 6502 low byte/high byte format.
**  See below for more details.
*/

/*
**  Get the byte that specifies the file type.
**  We might have to skip some bytes that were added by file transfers.
**  If this is the first header, we are reading the first byte, and we
**  cannot be expected to process junk, so then we must get either F9 or FA.
*/
    if( cnt_45 == 0 )   /* First header? */
    {
        file_type = get_byte();
        if( ( file_type != 0x0F9 ) && ( file_type != 0x0FA ) )
        {
            fprintf(stderr, "\n%s is not a valid Diskcomm archive,\n", dcm_path);
            fprintf(stderr, "it does not begin with F9 or FA.\n");
            return( FAILURE );
        }
        memset( sec_buf, 0x00, 256 );   /* Initialize sector buffer */
    } /* end if first header */
    else
    {

/*
**  The last thing we read from the file was the flush buffer code.
**  If this is an archive that consists of only one file, we must now
**  find the header for the first pass.  If it is a multi file archive,
**  there are two possibilities.  Either we will now reach the end of the
**  file, and we must close it and open the next file, or the user somehow
**  merged the files, and we can continue reading this file.  Either way,
**  file transfer may have added bytes to the end of the archive file.
**  Read bytes until we hit the header byte, or until we hit the end of
**  file marker.  At end of file, try to open the next file.
*/
        do      /* Read bytes until we find a header byte */
        {
            dcm_byte = fgetc( dcm_file );
            file_type = ((ubyte)(dcm_byte & 0x0FF));
            PRINT( ("Looking for header byte found %.2x\n", file_type ) );
            if( dcm_byte == EOF )
            {
                fclose( dcm_file );
                (dcm_path[increment])++;
                dcm_file = fopen( (char *)dcm_path, "rb" );
                PRINT( ("Attempting to open %s.\n", dcm_path ) );
                if( dcm_file == NULL )
                {
                    fprintf(stderr, "\nCannot open next Diskcomm archive file %s.\n",
                            dcm_path );
                    return( FAILURE );
                }
                dcm_byte = fgetc( dcm_file );
                if( dcm_byte == EOF )
                {
                    fprintf(stderr, "\nNext file %s\n", dcm_path);
                    fprintf(stderr, "is not a valid .dcm file, it does not begin with F9 or FA.\n");
                    return( FAILURE );
                }
                file_type = ((ubyte)(dcm_byte & 0x0FF));
                PRINT( ("Looking for header byte found %.2x\n", file_type ) );
                if( ( file_type != 0x0F9 ) && ( file_type != 0x0FA ) )
                {
                    fprintf(stderr, "\nNext file %s\n", dcm_path);
                    fprintf(stderr, "is not a valid .dcm file, it does not begin with F9 or FA.\n");
                    return( FAILURE );
                }
            }
        } while( ( file_type != 0x0F9 ) && ( file_type != 0x0FA ) );
    } /* End else if first header */

    if( file_type == 0x0F9 )
    {
        large = FALSE;
        cnt_F9++;
        memset( sec_buf, 0x00, 256 );   /* Initialize sector buffer */
    }
    else
    {
        large = TRUE;
        cnt_FA++;
    }

/*
**  Get the byte that specifies the pass count and the disk format.
**  If the high order bit is set, this is the last pass.
*/
    pass = get_byte();
    last = ( pass & 0x080 ) ? TRUE : FALSE;
    density = (( pass & 0x60 ) >> 5 ) + 1;
    pass &= 0x1F;

/*
**  The pass count should match the expected pass count, otherwise
**  this archive is corrupt.  In this case it was probably assembled out
**  of multiple smaller archive files.  Concatenating them in the proper
**  order is required for this program.
*/
    if( pass != pass_expected )
    {
        fprintf(stderr, "\nThe header indicated an out of sequence pass number.");
        fprintf(stderr, "\n%s is not a valid Diskcomm archive.\n", dcm_path);
        if( !large )
        {
            printf("Did you merge all parts of this Diskcomm archive with the COPY command?\n");
            printf("Did you forget the /B switch in the COPY command?\n");
            printf("Did you copy the files in the proper order?\n");
            printf("Did you read the documentation?\n");
        }
        return( FAILURE );
    }

/*
**  Well, we processed the header.
**  This looks like what we wanted, so we will call this success.
*/

    return( SUCCESS );
}

/*****************************************************************************
**  NAME:  usage()
**
**  PURPOSE:
**      Display the command line format for the program.
**
**  DESCRIPTION:
**      This function will explain the usage of the program to the user.
**      The program name is taken from the first command line argument.
**
**  INPUT:
**      - The address of the command line, containing the program name.
**
**  OUTPUT:
**      The usage is displayed on the terminal.
**      The function returns nothing.
**
*/

static void         usage( cmd )
char * cmd;                         /* Program name                     */
{
    char * whoami;      /* For searching program name in command line   */
    char * name;        /* Pointer to actual program name in command    */
    int    len;         /* Length of program name                       */
    int    found_dot;   /* Nonzero if we found a dot in the name        */

/*
**  Get program name and print usage message.
**  The complete pathname including extension is part of the first
**  argument as passed by the operating system.
*/
    for( whoami = cmd, len = 0, found_dot = 0; *whoami; whoami++ )
    {
        if( *whoami == '.' )
        {
            found_dot = 1;
            continue;
        }

/*
**  If this was part of the path
*/
        if( ( *whoami == '\\' ) || ( *whoami == '/' ) )
        {
            name = whoami + 1;  /* record position */
            len = 0;            /* then restart counting length */
            found_dot = 0;
            continue;
        }
        if( *whoami == ' ' )    /* end of name found            */
            break;
        if( found_dot )         /* skip .exe or .com stuff      */
            continue;
        len++;                  /* Increment program name length */
    }

/*
**  Let me explain...
*/
    fprintf(stderr, "\nUsage: %.*s [DCM file] [ATR file] [/d] [/h=nnnn] [/s]\n", len, name);
    fprintf(stderr, "to convert a .dcm file to an .atr disk image.\n\n");
    fprintf(stderr, "DCM file   a file created by the Diskcomm program.\n");
    fprintf(stderr, "ATR file   a file in the SIO2PC disk image format.\n\n");
    fprintf(stderr, "/d         to print diagnostic information.\n");
    fprintf(stderr, "/h=nnnn    highest sector number to expect,\n");
    fprintf(stderr, "           where nnnn is a number from 1 to 9999.\n");
    fprintf(stderr, "/s         to print statistics information.\n\n");
    fprintf(stderr, "Refer to the documentation for more information.\n");

    return;
}

/*****************************************************************************
==  EXPORTED FUNCTIONS
*****************************************************************************/

/*****************************************************************************
**  NAME:  MAIN()
**
**  PURPOSE:
**      An entry point for testing or running this utility.
**
**  DESCRIPTION:
**      Prompt for the file to open and then go process it.
**
**  INPUT:
**      argc and argv.
**
**  OUTPUT:
**      Returns an int as it should.
**
*/
int                 main( argc, argv )
int                 argc;               /* Command line argument count  */
char              * argv[];             /* Command line argument ptrs   */
{
    ubyte           answer;                 /* Response to yes/no question  */
    uint32          arg_ndx;                /* Argument number index        */
    uint32          arg_no;                 /* Argument number              */
    atr_blk         atr;                    /* The header for the atr image */
    uint32          atr_siz;                /* Size of ATR file excl. hdr   */
    uint32          wrk_ndx;                /* Work index                   */
    bool            end_of_str;             /* Null terminator seen?        */
    uint32          hdr = 'N'+'I'+'C'+'K' +
                          'A'+'T'+'A'+'R'+'I';
    ubyte           buf[BUF_LEN];           /* Buffer string                */
    uint32          stat;                   /* Status from function         */
    ubyte           proceed;                /* Proceed with conversion      */

/*
**  Set default values for unused command switches.
*/
    atr_path[0] = 0x00;
    dcm_path[0] = 0x00;
    diagnostics = FALSE;
    statistics = FALSE;
    max_sector = 0;
    pass_cnt = 0;
    pass_expected = 0;

/*
**  Process command line arguments.
**  We do not treat the options switch as an argument.  It may be placed
**  anywhere on the command line.  So we have to count the arguments ourselves
**  so that we know what argument we are processing.
**  If this program is ported to the Atari ST, the program name is not
**  on the command line, so the program should then be adapted.
*/
    arg_no = 0;

    for( arg_ndx = 1; arg_ndx < argc; arg_ndx++ )
    {

/*
**  If we encounter the options switch, process the options.
**  The options must start with a slash.
*/
        if( ( argv[arg_ndx][0] == '/' ) || ( argv[arg_ndx][0] == '-' ) )
        {

            for( wrk_ndx = 0; argv[arg_ndx][wrk_ndx]; wrk_ndx++ )
            {

/*
**  If the user is confused, seeking help, she/he should read the * manual.
**  We can give them a hint though.
*/
                if( argv[arg_ndx][wrk_ndx] == '?' )
                {
                    usage( argv[0] );
                    panic( 0 );         /* Not really an error */
                }

/*
**  The /d option selects the diagnostics output.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'D' )
                {
                    diagnostics = TRUE;
                    continue;
                }

/*
**  The /h option selects the highest sector number we should expect.
**  Diskcomm calls this the Maximum sector number your drive is capable
**  of reading.  This happens to be option H in the menu.
**  The format of this switch is /h=nnnn where nnnn is a numeric value
**  that within Diskcomm can range from 1 to 9999.  We accept any number
**  the user enters though.  This option is only needed for non-standard
**  disk sizes, where we want the proper number of sectors with zeroes
**  at the end of the disk image.  This cannot be used to truncate a disk.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'H' )
                {
                    while( argv[arg_ndx][++wrk_ndx] )
                    {
                        if( ( argv[arg_ndx][wrk_ndx] >= '0' ) &&
                            ( argv[arg_ndx][wrk_ndx] <= '9' ) )
                        {
                            max_sector *= 10;
                            max_sector += argv[arg_ndx][wrk_ndx] - '0';
                        }
                    }
                    break;
                }

/*
**  The /s option selects the statistics output.
*/
                if( toupper( argv[arg_ndx][wrk_ndx] ) == 'S' )
                {
                    statistics = TRUE;
                    continue;
                }

/*
**  Ignore other options.
*/
                continue;
            } /* end for all characters after options switch */

/*
**  No further processing for the options switches.
*/
            continue;
        } /* end if options switch */
        arg_no++;

/*
**  First argument is the file spec for the Diskcomm file.
*/
        if( arg_no == 1 )
        {
            for ( wrk_ndx = 0, end_of_str = FALSE;
                wrk_ndx < PATH_LEN; wrk_ndx++ )
            {
                if ( argv[arg_ndx][wrk_ndx] == '\0' ) /* End of argument string?            */
                    end_of_str = TRUE;
                if ( end_of_str )
                    dcm_path[wrk_ndx] = '\0';
                else
                    dcm_path[wrk_ndx] = toupper( argv[arg_ndx][wrk_ndx] );
            }
        }

/*
**  Optionally the name of the SIO2PC image file can be entered as the second
**  command line argument.  If it is not specified, the input path is copied,
**  and the extension is replaced by .atr.
*/
        if( arg_no == 2 )
        {
            for ( wrk_ndx = 0, end_of_str = FALSE;
                wrk_ndx < PATH_LEN; wrk_ndx++ )
            {
                if ( argv[arg_ndx][wrk_ndx] == '\0' ) /* End of argument string?            */
                    end_of_str = TRUE;
                if ( end_of_str )
                    atr_path[wrk_ndx] = '\0';
                else
                    atr_path[wrk_ndx] = toupper( argv[arg_ndx][wrk_ndx] );
            }

        }
    } /* end for all command line arguments */

/*
**  If there is no filename on the command line, ask for it.
*/
    if( ( arg_no == 0 ) || ( dcm_path[0] == 0x00 ) )
    {

/*
**  Open hailing frequencies.
**  No command line arguments, so ask what it is we have to do.
*/
        printf( "\n\nDiskcomm DCM archive to SIO2PC ATR disk image converter.\n" );
        printf( "Version February 2, 1998\n" );
        printf( "\nCopyright 1998 by Ernest R. Schreurs\n" );
        printf( "\nAll rights reserved\n" );


        while( TRUE )                       /* until terminated by control Z*/
        {
            printf( "\nEnter ^Z or hit Control Z to terminate\n" );
            if( dcm_file )
            {
                fclose( dcm_file );
                dcm_file = NULL;
            }
            if( atr_file )
            {
                fclose( atr_file );
                atr_file = NULL;
            }

            do                              /* until .dcm file entered    */
            {
                printf("\nEnter .dcm file to be converted : ");
                GET_BUF();

                for ( wrk_ndx = 0, end_of_str = FALSE;
                    wrk_ndx < PATH_LEN; wrk_ndx++ )
                {
                    if ( wrk_ndx < BUF_LEN )
                    {
                        if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                            end_of_str = TRUE;
                        if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                            end_of_str = TRUE;
                        if ( end_of_str )
                            dcm_path[wrk_ndx] = '\0';
                        else
                            dcm_path[wrk_ndx] = toupper( buf[wrk_ndx] );
                    }
                }
            } while ( dcm_path[0] == ' ' );

            do                              /* until answer is Y or N       */
            {
                printf("\nConvert file %s\n", dcm_path);
                printf("\nIs this correct Y)es or N)o : ");
                GET_BUF();
                proceed = toupper( buf[0] );

/*
**  If blank, default is correct
*/
                if ( proceed == '\n' )
                    proceed = 'Y';

            } while ( proceed != 'Y' && proceed != 'N' );

            if ( proceed == 'N' )
                continue;

            dcm_file = fopen( (char *)dcm_path, "rb" );
            if( dcm_file == NULL )
            {
                fprintf(stderr, "Cannot open Diskcomm file\n");
                continue;
            }

            do                              /* until .atr file entered    */
            {
                printf("\nEnter .atr file to be created : ");
                GET_BUF();

                for ( wrk_ndx = 0, end_of_str = FALSE;
                    wrk_ndx < PATH_LEN; wrk_ndx++ )
                {
                    if ( wrk_ndx < BUF_LEN )
                    {
                        if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                            end_of_str = TRUE;
                        if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                            end_of_str = TRUE;
                        if ( end_of_str )
                            atr_path[wrk_ndx] = '\0';
                        else
                            atr_path[wrk_ndx] = toupper( buf[wrk_ndx] );
                    }
                }
            } while ( atr_path[0] == ' ' );

            do                              /* until answer is Y or N       */
            {
                printf("\nConvert file %s to %s\n", dcm_path, atr_path);
                printf("\nIs this correct Y)es or N)o : ");
                GET_BUF();
                proceed = toupper( buf[0] );

/*
**  If blank, default is correct
*/
                if ( proceed == '\n' )
                    proceed = 'Y';

            } while ( proceed != 'Y' && proceed != 'N' );

            if ( proceed == 'N' )
                continue;

/*
**  Check to see that we can open the file.
*/
            atr_file = fopen( (char *)atr_path, "rb" );
            if( atr_file != NULL )
            {
                fprintf(stderr, "Specified SIO2PC image file %s already exists.\n", atr_path);
                fclose( atr_file );
                continue;
            }
            atr_file = fopen( (char *)atr_path, "wb+" );
            if( atr_file == NULL )
            {
                fprintf(stderr, "Cannot open SIO2PC image file %s\n", atr_path);
                continue;
            }

/*
**  Ask for diagnostics option.
*/
            do                              /* until answer is Y or N       */
            {
                printf("\nPrint diagnostic data Y)es or N)o : ");
                GET_BUF();
                answer = toupper( buf[0] );

/*
**  If blank, default is no diagnostics
*/
                if ( answer == '\n' )
                    answer = 'N';

            } while ( answer != 'Y' && answer != 'N' );

            diagnostics = ( answer == 'Y' ) ? TRUE : FALSE;

/*
**  Ask for statistics option.
*/
            do                              /* until answer is Y or N       */
            {
                printf("\nPrint statistics Y)es or N)o : ");
                GET_BUF();
                answer = toupper( buf[0] );

/*
**  If blank, default is print statistics
*/
                if ( answer == '\n' )
                    answer = 'Y';

            } while ( answer != 'Y' && answer != 'N' );

/*
**  Ask for highest sector number.
*/
            printf("\nEnter highest sector number : ");
            GET_BUF();
            max_sector = 0;

            for( wrk_ndx = 0; wrk_ndx < BUF_LEN; wrk_ndx++ )
            {
                if ( buf[wrk_ndx] == '\n' ) /* End of inputted string?      */
                    break;
                if ( buf[wrk_ndx] == '\0' ) /* Overkill, End marked by \n   */
                    break;
                if( ( buf[wrk_ndx] >= '0' ) &&
                    ( buf[wrk_ndx] <= '9' ) )
                {
                    max_sector *= 10;
                    max_sector += buf[wrk_ndx] - '0';
                }
            }

            printf( "\nConverting data from Diskcomm file.\n" );
            break;
        } /* end while need a valid filename */
    } /* end if no command line arguments */
    else
    {

/*
**  Replace .dcm extension by .atr extension, or add .atr if there is
**  no extension at all.  If it all fits that is.
**  If the user really wants, it is possible to make this code fail.
**  That is their loss.
*/
        if( atr_path[0] == 0x00 )
        {
            memcpy( atr_path, dcm_path, PATH_LEN );
            wrk_ndx = STRLEN( atr_path );
            if( wrk_ndx > 4 )
                if( atr_path[wrk_ndx - 4] == '.' )
                    wrk_ndx -= 4;
            if( wrk_ndx + 5 <= PATH_LEN )
                memcpy( &(atr_path[wrk_ndx]), ".ATR", 5 );
        }

/*
**  Try to open the file that was specified on the command line.
*/
        dcm_file = fopen( (char *)dcm_path, "rb" );
        if( dcm_file == NULL )
        {
            fprintf(stderr, "Cannot open Diskcomm file %s\n", dcm_path);
            panic( 255 );
        }

        atr_file = fopen( (char *)atr_path, "rb" );
        if( atr_file != NULL )
        {
            fprintf(stderr, "Specified SIO2PC image file %s already exists.\n", atr_path);
            fclose( atr_file );
            panic( 255 );
        }
        atr_file = fopen( (char *)atr_path, "wb+" );
        if( atr_file == NULL )
        {
            fprintf(stderr, "Cannot open SIO2PC image file %s\n", atr_path);
            panic( 255 );
        }
    } /* end else if no command line arguments */

/*
**  For multi file archives, determine the character to be incremented
**  in the path specification.
**  Try to find a "1" or an "A" starting at the end of the path
**  specification, working our way back.
**  If we hit the directory marker, use the last character before the file
**  extension.  If there is no extension, us the last character in the
**  filename.
*/
    increment = PATH_LEN;

    for( wrk_ndx = STRLEN( dcm_path); wrk_ndx--; )
    {
        if( ( dcm_path[wrk_ndx] == '1' ) ||
            ( dcm_path[wrk_ndx] == 'A' ) )
        {
            increment = wrk_ndx;
            break;
        }
        if( ( dcm_path[wrk_ndx] == '.' ) &&
            ( wrk_ndx > 0 ) )
        {
            increment = wrk_ndx - 1;
            continue;
        }
        if( ( dcm_path[wrk_ndx] == '\\' ) ||
           ( dcm_path[wrk_ndx] == '/' ) )
        {
            if( increment == PATH_LEN )
                increment = STRLEN( dcm_path );
            break;
        }
    }

/*
**  Initialize some stuff.
*/
    memset( sec_zer, 0x00, 256 );
    sector_atr = 0;
    cnt_41 = 0;
    cnt_42 = 0;
    cnt_43 = 0;
    cnt_44 = 0;
    cnt_45 = 0;
    cnt_46 = 0;
    cnt_47 = 0;
    cnt_F9 = 0;
    cnt_FA = 0;

    do          /* until last pass processed */
    {

/*
**  Process the header of the .dcm file.
*/
        pass_cnt++;
        if( pass_cnt & 0x20 )
            pass_expected = pass_cnt - 1;       /* For some odd reason */
        else
            pass_expected = pass_cnt;           /* Pass we should find next */
        pass_expected &= 0x1F;

        stat = process_header();
        if( stat == FAILURE )
        {
            panic( 255 );
        }

        PRINT( ("File : %s\n", dcm_path ) );
        PRINT( ("Density: %u\n", density ) );

/*
**  Based on the density, compute the number of sectors and the
**  sector size.  Note that the user can modify the highest sector number
**  as one of the options in Diskcomm.  So these sector counts are only
**  defaults.  Diskcomm might even be able to compress an entire hard disk.
*/
        switch( density )
        {
            case DENSITY_SD:
                sectors = 720;
                sec_size = 128;
                break;
            case DENSITY_ED:
                sectors = 1040;
                sec_size = 128;
                break;
            case DENSITY_DD:
                sectors = 720;
                sec_size = 256;
                break;
            default:
                sectors = 720;
                sec_size = 128;
                break;
        }

/*
**  If non-standard size specified, use it instead.
*/
        if( max_sector )
            sectors = max_sector;

/*
**  The size of the ATR file is specified in paragraphs, a paragraph on the
**  PC is 16 bytes.  The size of the header, which is always 16 bytes,
**  is not included in this size value.  The size of the ATR file can thus
**  be computed by multiplying the total number of sectors by the sector size,
**  and dividing this value by 16.  Unless it is a double density disk.
**  The first three sectors of any Atari double density disk are always 
**  128 bytes in size, so that the machine can always boot these three
**  sectors.  In ATR files, these three sectors are stored as 128 bytes
**  of data.  So this must be subtracted to compute the proper size.
*/
        atr_siz = sectors * sec_size;
        if( density == DENSITY_DD )
        {
            if( sectors > 3 )
                atr_siz -= 3 * 128;
            else
                atr_siz = sectors * 128;
        }
        atr_siz = atr_siz >> 4;     /* Convert to paragraphs, divide by 16 */


/*
**  Write the header to the SIO2PC image file.
*/
        if( sector_atr == 0 )
        {
            memset( &atr, 0x00, sizeof( atr_blk ) );
            atr.atr_hdr_lo = hdr;           /* Header, sum of NICKATARI     */
            atr.atr_hdr_hi = hdr >> 8;
            atr.atr_siz_lo = atr_siz;       /* Size of atr excluding header */
            atr.atr_siz_hi = atr_siz >> 8;
            atr.atr_sec_size_lo = sec_size; /* Sector size                  */
            atr.atr_sec_size_hi = sec_size >> 8;
            atr.atr_hi_size_lo = atr_siz >> 16; /* High order part of size  */
            atr.atr_hi_size_hi = atr_siz >> 24;
            atr.atr_d_info = 0;         /* Copy protect/write protect flags */
            atr.atr_sec_lo = 0;         /* Sector number of typical copy    */
            atr.atr_sec_hi = 0;         /* protected sector, low/high       */

            fwrite( &atr, 1, sizeof( atr_blk ), atr_file );
        }

/*
**  Process the data portion of the .dcm file.
*/
        stat = dcm2atr();

    } while( !last );

/*
**  Make sure the last sectors of the disk are zeroes.
*/
    while( ++sector_atr <= sectors )
    {
        fwrite( sec_zer, 1, (sector_atr < 4) ? 128 : sec_size, atr_file );
    }

/*
**  If this is a non-standard diskette size, the actual size must be
**  adjusted in the header.  We have no way of knowing the physical
**  ending sector, if the user did not specify this.  In that case, the
**  last non-zero sector determines the disk size.  If the user did
**  specify this, this will have been recorded in the header already,
**  and we only have to adjust it if the user made a mistake.
*/
    sector_atr--;
    if( sector_atr > sectors )
    {
        atr_siz = sector_atr * sec_size;        /* Compute real size */
        if( density == DENSITY_DD )
        {
            if( sector_atr > 3 )
                atr_siz -= 3 * 128;
            else
                atr_siz = sector_atr * 128;
        }
        atr_siz = atr_siz >> 4;     /* Convert to paragraphs, divide by 16 */

/*
**  Update header and write it out again.
*/
        atr.atr_siz_lo = atr_siz;           /* Size of atr excluding header */
        atr.atr_siz_hi = atr_siz >> 8;
        atr.atr_hi_size_lo = atr_siz >> 16; /* High order portion of size   */
        atr.atr_hi_size_hi = atr_siz >> 24;

        fseek( atr_file, 0L, SEEK_SET );    /* Go back to beginning of file */
        fwrite( &atr, 1, sizeof( atr_blk ), atr_file );
    }

    cleanup();

    if( statistics )
    {
        printf("\nStatistics for Diskcomm archive file %s:\n\n", dcm_path );
        switch( density )
        {
            case DENSITY_SD:
                printf("Single density");
                break;
            case DENSITY_DD:
                printf("Double density");
                break;
            case DENSITY_ED:
                printf("Enhanced density");
                break;
            default:
                printf("Unknown density");
                break;
        }
        printf(" %lu Kbyte.\n", atr_siz >> 6 );
        printf("FA large header   : %lu\n", cnt_FA );
        printf("F9 small header   : %lu\n", cnt_F9 );
        printf("41 modify begin   : %lu\n", cnt_41 );
        printf("42 123 byte/5 byte: %lu\n", cnt_42 );
        printf("43 compressed     : %lu\n", cnt_43 );
        printf("44 modify end     : %lu\n", cnt_44 );
        printf("45 flush buffer   : %lu\n", cnt_45 );
        printf("46 identical      : %lu\n", cnt_46 );
        printf("47 uncompressed   : %lu\n", cnt_47 );
        printf("   Total number   : %lu\n", cnt_41 + cnt_42 + cnt_43 +
                                            cnt_44 + cnt_46 + cnt_47 );
        printf("   Disk sectors   : %u\n", sector_atr );

        printf("\nDone processing.\n");
    }

    return 0;
}

/*****************************************************************************
**  MODIFICATION HISTORY
**
**  DATE        BY   Description
**  ----------  ---  ---------------------------------------------------------
**  1998/01/04  ERS  Start coding, after many hours of research
**  1998/01/18  ERS  Release 01.00
**  1998/02/02  ERS  Release 01.01 Initialize previous sector buffer when
**                   processing next pass for multi-file archives.
**
*****************************************************************************/
