#if defined(WIN32)
#include <windows.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <io.h>
#include <dos.h>
#include <malloc.h>
#include <string.h>

#pragma hdrstop

#include <ctype.h>

#include "cdrlib.h"
#include "iso9660.h"

// 1 byte alignment throughout this file.

#pragma pack(push, 1)

// Externals.

extern SWORD gCDFSTimezoneOffset;
extern SYSDATETIME gCDFSFileDate;
extern BOOL gCDFSFileDateValid;

// Statics.

static char dateformat_str[] = "%4u%02u%02u%02u%02u%02u00";

//
// Convert date and time to/from ISO9660 format.
//

void ISO9660ConvertDate(SYSDATETIME *datetimeP, ISO9660DATETIME *ISOdateP)
{
  ISOdateP->year = datetimeP->date.year - 1900;   // Years since 1900
  ISOdateP->month = datetimeP->date.month;
  ISOdateP->day = datetimeP->date.day;

  ISOdateP->hour = datetimeP->time.hour;
  ISOdateP->minute = datetimeP->time.minute;
  ISOdateP->second = datetimeP->time.second;

  ISOdateP->gmt_offset =
    ((gCDFSTimezoneOffset == CDFS_TIMEZONE_UNKNOWN) ? 0 : -(gCDFSTimezoneOffset / 15));
}

void ISO9660ConvertDate(ISO9660DATETIME *ISOdateP, SYSDATETIME *datetimeP)
{
  datetimeP->date.year = ISOdateP->year + 1900;   // Years since 1900
  datetimeP->date.month = ISOdateP->month;
  datetimeP->date.day = ISOdateP->day;

  datetimeP->time.hour = ISOdateP->hour;
  datetimeP->time.minute = ISOdateP->minute;
  datetimeP->time.second = ISOdateP->second;
}

//
// Initialize the root directory record
//

void ISO9660InitRootDirectory(
  ISO9660DIRECTORYREC *isodirP, SYSDATETIME *datetimeP, SLONG lba, ULONG blkcnt)
{
  isodirP->record_length = ISO9660_DIR_RECORD_LEN(1);
  isodirP->ext_attr_blkcnt = 0;
  ConvertLongToLEBE (lba, (ULONG *)isodirP->lba);
  ConvertLongToLEBE (blkcnt * SECTOR_MODE1_BLKLEN, isodirP->data_length);
  ISO9660ConvertDate ((gCDFSFileDateValid ? &gCDFSFileDate : datetimeP), &isodirP->date);
  isodirP->flags = ISO9660_M_DIRECTORY;
  isodirP->file_unit_size = 0;
  isodirP->interleave = 0;
  ConvertWordToLEBE (1, isodirP->volume_seq_number);
  isodirP->name_length = 1;
  isodirP->name[0] = 0;
}

//
// Assign sectors to all directories
//

void ISO9660AssignDirectorySectors(
  DirectoryTree *dirtreeP, SLONG *lbaP, BOOL long_filenames_flag, BOOL noversion_flag)
{ 
  int d;

  // Sort the directory tree by the short filenames and assign a beginning sector
  // to each ISO9660 directory (these are assigned on a depth ordered basis).

  dirtreeP->Sort(FALSE);

  for (d = 1; d <= dirtreeP->nMaxDirDepth; d++)
    ISO9660AssignDirectorySectorsRecurse (dirtreeP->pRootDirNode, d, lbaP, FALSE, noversion_flag);

  // If necessary, sort the directory tree by the long filenames and assign a beginning sector
  // to each Joliet directory (these are assigned on a depth ordered basis).

  if (long_filenames_flag)
    {
    dirtreeP->Sort(TRUE);

    for (d = 1; d <= dirtreeP->nMaxDirDepth; d++)
      ISO9660AssignDirectorySectorsRecurse (dirtreeP->pRootDirNode, d, lbaP, TRUE, noversion_flag);
    }
}
  
void ISO9660AssignDirectorySectorsRecurse(
  DIRNODE *dirnodeP, UBYTE depth, SLONG *lbaP, BOOL long_filenames_flag, BOOL noversion_flag)
{ 
  // Assign sectors to all directories at the specified depth.

  if (dirnodeP->depth == depth)
    {
    // Compute the length of the directory.

    UWORD blkcnt = ISO9660ComputeDirectoryLength (dirnodeP, long_filenames_flag, noversion_flag);

    // Assign the next available LBA.
   
   if (long_filenames_flag)
      {
      dirnodeP->joliet_blkcnt = blkcnt;
      dirnodeP->parent_filenodeP->joliet_size = blkcnt * SECTOR_MODE1_BLKLEN;

      dirnodeP->joliet_lba = *lbaP;
      dirnodeP->parent_filenodeP->joliet_lba = *lbaP;
      *lbaP += blkcnt;
      }
    else
      {
      dirnodeP->iso9660_blkcnt = blkcnt;
      dirnodeP->parent_filenodeP->iso9660_size = blkcnt * SECTOR_MODE1_BLKLEN;

      dirnodeP->iso9660_lba = *lbaP;
      dirnodeP->parent_filenodeP->iso9660_lba = *lbaP;
      *lbaP += blkcnt;
      }

    // We don't need to recurse any further.

    return;
    }

  // Recurse through the subdirectories...

  ABSQ_LOOP (&dirnodeP->subdir_qhd, dirP, DIRNODE, nextdir)
    ISO9660AssignDirectorySectorsRecurse (dirP, depth, lbaP, long_filenames_flag, noversion_flag);
  ABSQ_POOL
}

//
// Compute the number of blocks required for a directory.
//

UWORD ISO9660ComputeDirectoryLength(DIRNODE *dirnodeP, BOOL long_filenames_flag, BOOL noversion_flag)
{
  // The directory must be at least one block in length.

  UWORD dir_blkcnt = 1;

  // Account for the length of the "." and ".." directories.

  int rem_sector_len = SECTOR_MODE1_BLKLEN - (ISO9660_DIR_RECORD_LEN(1) * 2);

  // Compute the length of the directory (in sectors).

  ABSQ_LOOP (&dirnodeP->file_qhd, filenodeP, FILENODE, nextfile)
    {
    // Get a pointer to the filename.

    char *nameP = filenodeP->GetFileName(long_filenames_flag);

    // Compute the number of characters in the filename.

    int filnam_len = StringCharCount(nameP);

    // If necessary, add an extension and version number to the filename.

    if (! filenodeP->dir_flag)
      {
      // If there is no extension on the filename, then we will add "." to the end.
      if (strchr(nameP, '.') == NULL) filnam_len++;

      // Optionally add two to the length of all filenames for the ";1" version number.
      if (! noversion_flag) filnam_len += 2;
      }

    // Joliet names are twice as long because they are in Unicode format.

    if (long_filenames_flag) filnam_len *= 2;

    // Compute the length of this record.

    int record_length = ISO9660_DIR_RECORD_LEN(filnam_len);
  
    // If the record length is too long, then signal an exception.

    if (record_length > ISO9660_MAX_DIR_RECORD_LENGTH)
      SignalException (E_FilenameTooLong, 0, E_Generic, 1, HeapString(nameP));

    // If there is not enough room in the current sector, then start a new one
    // (directory records cannot span sector boundaries).

    if (record_length > rem_sector_len)
      {
      dir_blkcnt++;
      rem_sector_len = SECTOR_MODE1_BLKLEN;
      }

    // Decrement the amount of space left in this sector.

    rem_sector_len = rem_sector_len - record_length;
    }
  ABSQ_POOL

  // Return the block count.

  return (dir_blkcnt);
}

//
// Compute the length of the pathtable.
//

ULONG ISO9660ComputePathtableLength(DirectoryTree *dirtreeP, BOOL long_filenames_flag)
{
  ULONG length = 0;
  ISO9660ComputePathtableLengthRecurse (dirtreeP->pRootDirNode, long_filenames_flag, &length);
  return (length);
}

void ISO9660ComputePathtableLengthRecurse(DIRNODE *dirnodeP, BOOL long_filenames_flag, ULONG *lengthP)
{
  // Compute the length of the directory name. Joliet names are twice as long
  // because they are in Unicode format.

  char *nameP = dirnodeP->GetDirName(long_filenames_flag);
  UWORD namelen = StringCharCount(nameP) * (long_filenames_flag ? 2 : 1);

  // Compute the record length for this directory.

  *lengthP += ISO9660_PATHTABLE_RECORD_LEN(namelen);

  // Recurse through the rest of the tree...

  ABSQ_LOOP (&dirnodeP->subdir_qhd, dirP, DIRNODE, nextdir)
    ISO9660ComputePathtableLengthRecurse (dirP, long_filenames_flag, lengthP);
  ABSQ_POOL
}

//
// Build an ISO9660 pathtable.
//

void ISO9660BuildPathtable(
  DirectoryTree *dirtreeP, void *pathtableP, BOOL long_filenames_flag, BOOL big_endian)
{
  ISO9660PATHTABLEREC *loc_pathtableP = (ISO9660PATHTABLEREC *)pathtableP;

  // Initialize the record number.
  UWORD recnum = 1;

  // Sort the directory tree.
  dirtreeP->Sort(long_filenames_flag, TRUE);

  // Build the pathtable.
  for (UBYTE d = 1; d <= dirtreeP->nMaxDirDepth; d++)
    ISO9660BuildPathtableRecurse (
      dirtreeP->pRootDirNode, &loc_pathtableP, &recnum, long_filenames_flag, big_endian, d);
}

void ISO9660BuildPathtableRecurse(
  DIRNODE *dirnodeP, ISO9660PATHTABLEREC **pathtablePP, UWORD *recnumP,
  BOOL long_filenames_flag, BOOL big_endian, UBYTE depth)
{
  // Only process directories at this depth...

  if (dirnodeP->depth == depth)
    {
    ISO9660PATHTABLEREC *pathtableP = *pathtablePP;

    // Assign a pathtable record number to this directory.

    dirnodeP->pathtable_recnum = (*recnumP)++;

    // Get a pointer to the directory name.

    char *nameP = dirnodeP->GetDirName(long_filenames_flag);
    
    // Compute the name length.

    UWORD dirnam_len = ((depth == 1) ? 1 : (StringCharCount(nameP) * (long_filenames_flag ? 2 : 1)));

    // Compute the record length.

    int reclen = ISO9660_PATHTABLE_RECORD_LEN(dirnam_len);
    if (reclen > 255) SignalException (E_FilenameTooLong);

    pathtableP->name_length = dirnam_len;
    pathtableP->ext_attr_blkcnt = 0;

    // Initialize the LBA and record number.

    SLONG lba = (long_filenames_flag ? dirnodeP->joliet_lba : dirnodeP->iso9660_lba);

    if (big_endian)
      {
      ConvertLongToBE (lba, (ULONG *)&pathtableP->dir_lba);
      ConvertWordToBE (dirnodeP->parent_dirnodeP->pathtable_recnum, &pathtableP->parent_recnum);
      }
    else
      {
      ConvertLongToLE (lba, (ULONG *)&pathtableP->dir_lba);
      ConvertWordToLE (dirnodeP->parent_dirnodeP->pathtable_recnum, &pathtableP->parent_recnum);
      }

    // If this is the root directory, then the name is zero.

    if (depth == 1)
      pathtableP->name[0] = 0;
    else
      {
      if (long_filenames_flag)
        ConvertStringToUnicode (pathtableP->name, nameP, strlen(nameP), TRUE);
      else
        memcpy (pathtableP->name, nameP, strlen(nameP));
      }

    *pathtablePP = (ISO9660PATHTABLEREC *)((UBYTE *)*pathtablePP + reclen);

    // We don't need to recurse any further...

    return;
    }

  // Recurse through the rest of the tree...

  ABSQ_LOOP (&dirnodeP->subdir_qhd, dirP, DIRNODE, nextdir)
    ISO9660BuildPathtableRecurse (dirP, pathtablePP, recnumP, long_filenames_flag, big_endian, depth);
  ABSQ_POOL
}

//
// Build the ISO9660 directories
//

void ISO9660BuildDirectories(
  DirectoryTree *dirtreeP, CDFSOPTIONS *cdfs_optionsP, CDFSWRITECALLBACK *callback, BOOL log_flag)
{
  int d;

  BOOL long_filenames_flag = cdfs_optionsP->long_filenames_flag;
  BOOL noversion_flag = cdfs_optionsP->noversion_flag;

  // Sort the directory tree by the short filenames and build the standard ISO9660
  // directory structures (these are built on a depth ordered basis).

  dirtreeP->Sort(FALSE);

  for (d = 1; d <= dirtreeP->nMaxDirDepth; d++)
    ISO9660BuildDirectoriesRecurse (
      dirtreeP->pRootDirNode, callback, d, FALSE, noversion_flag, log_flag);

  // If necessary, sort the directory tree by the long filenames and build the Joliet
  // directory structures (these are built on a depth ordered basis).

  if (long_filenames_flag)
    {
    dirtreeP->Sort(TRUE);

    for (d = 1; d <= dirtreeP->nMaxDirDepth; d++)
      ISO9660BuildDirectoriesRecurse (
        dirtreeP->pRootDirNode, callback, d, TRUE, noversion_flag, log_flag);
    }
}

void ISO9660BuildDirectoriesRecurse(
  DIRNODE *dirnodeP, CDFSWRITECALLBACK *callback, UBYTE depth,
  BOOL long_filenames_flag, BOOL noversion_flag, BOOL log_flag)
{
  // Only build directories at the specified depth.

  if (dirnodeP->depth == depth)
    {
    // Build the ISO9660 directory.
    ISO9660BuildOneDirectory (dirnodeP, callback, long_filenames_flag, noversion_flag, log_flag);

    // We don't need to recurse any further...
    return;
    }

  // Recurse through the subdirectories...

  ABSQ_LOOP (&dirnodeP->subdir_qhd, dirP, DIRNODE, nextdir)
    ISO9660BuildDirectoriesRecurse (dirP, callback, depth, long_filenames_flag, noversion_flag, log_flag);
  ABSQ_POOL
}

//
// Build one ISO9660 directory
//

void ISO9660BuildOneDirectory(
  DIRNODE *dirnodeP, CDFSWRITECALLBACK *callback,
  BOOL long_filenames_flag, BOOL noversion_flag, BOOL log_flag)
{
  int record_length;

  // Allocate an I/O buffer (automatically deallocated).

  IOBuffer iobuffer(sizeof(IOBUF));
  IOBUF *iobufP = (IOBUF *)iobuffer.GetHandle();

  // Initialize the buffer context.

  ISO9660DIRECTORYREC *isodirP = (ISO9660DIRECTORYREC *)iobufP->data;

  UWORD init_blkcnt = 0;
  UWORD rem_sector_len = SECTOR_MODE1_BLKLEN;

  UWORD blocks_per_io = sizeof(iobufP->data) / SECTOR_MODE1_BLKLEN;
  
  UWORD dir_blkcnt = (long_filenames_flag ? dirnodeP->joliet_blkcnt : dirnodeP->iso9660_blkcnt);
  UWORD written_blkcnt = 0;

  // Initialize context for the '.' and '..' directories.

  record_length = ISO9660_DIR_RECORD_LEN(1);

  FILENODE *parent_filenodeP = dirnodeP->parent_filenodeP;
  DIRNODE *parent_dirnodeP = dirnodeP->parent_dirnodeP;

  // Add an entry for the '.' directory.

  isodirP->record_length = record_length;
  isodirP->ext_attr_blkcnt = 0;

  if (long_filenames_flag)
    {
    ConvertLongToLEBE (dirnodeP->joliet_lba, (ULONG *)isodirP->lba);
    ConvertLongToLEBE (dirnodeP->joliet_blkcnt * 2048, isodirP->data_length);
    }
  else
    {
    ConvertLongToLEBE (dirnodeP->iso9660_lba, (ULONG *)isodirP->lba);
    ConvertLongToLEBE (dirnodeP->iso9660_blkcnt * 2048, isodirP->data_length);
    }

  ISO9660ConvertDate (
    (gCDFSFileDateValid ? &gCDFSFileDate : &parent_filenodeP->modified_datetime), &isodirP->date);

  isodirP->flags = ISO9660_M_DIRECTORY;
  isodirP->file_unit_size = 0;
  isodirP->interleave = 0;
  ConvertWordToLEBE (1, isodirP->volume_seq_number);
  isodirP->name_length = 1;
  isodirP->name[0] = 0;

  isodirP = (ISO9660DIRECTORYREC *)((UBYTE *)isodirP + record_length);
  rem_sector_len = rem_sector_len - record_length;

  // Add an entry for the '..' directory.

  isodirP->record_length = record_length;
  isodirP->ext_attr_blkcnt = 0;

  if (long_filenames_flag)
    {
    ConvertLongToLEBE (parent_dirnodeP->joliet_lba, (ULONG *)isodirP->lba);
    ConvertLongToLEBE (parent_dirnodeP->joliet_blkcnt * 2048, isodirP->data_length);
    }
  else
    {
    ConvertLongToLEBE (parent_dirnodeP->iso9660_lba, (ULONG *)isodirP->lba);
    ConvertLongToLEBE (parent_dirnodeP->iso9660_blkcnt * 2048, isodirP->data_length);
    }

  ISO9660ConvertDate (
    (gCDFSFileDateValid ? &gCDFSFileDate : &parent_filenodeP->modified_datetime), &isodirP->date);

  isodirP->flags = ISO9660_M_DIRECTORY;
  isodirP->file_unit_size = 0;
  isodirP->interleave = 0;
  ConvertWordToLEBE (1, isodirP->volume_seq_number);
  isodirP->name_length = 1;
  isodirP->name[0] = 1;

  isodirP = (ISO9660DIRECTORYREC *)((UBYTE *)isodirP + record_length);
  rem_sector_len = rem_sector_len - record_length;

  // Build the rest of the directory.

  ABSQ_LOOP (&dirnodeP->file_qhd, filenodeP, FILENODE, nextfile)
    {
    BOOL add_extension_flag = FALSE;
    BOOL add_version_flag = FALSE;

    // Get a pointer to the filename.

    char *nameP = filenodeP->GetFileName(long_filenames_flag);

    // Compute the name length...

    int filnam_len = StringCharCount(nameP);

    // If necessary, add an extension and version number to the filename.

    if (! filenodeP->dir_flag)
      {
      // If there is no extension on the filename, then we will add "." to the end.
      if (strchr(nameP, '.') == NULL)
        {filnam_len++; add_extension_flag = TRUE;}

      // Optionally add two to the length of all filenames for the ";1" version number.
      if (! noversion_flag)
        {filnam_len += 2; add_version_flag = TRUE;}
      }

    // Joliet names are twice as long because they are in Unicode format.

    if (long_filenames_flag) filnam_len *= 2;

    // Compute the length of this record.

    record_length = ISO9660_DIR_RECORD_LEN(filnam_len);

    // If there is not enough room in the current sector, then zero pad the
    // sector and start a new one (directory records cannot span a sector
    // boundary). We also might need to flush the current buffer if we've
    // reached the 64K limit.

    if (record_length > rem_sector_len)
      {
      // Zero fill the sector and increment the number of blocks initialized.

      CH_FILL (0, rem_sector_len, isodirP);
      init_blkcnt++;

      // If we've reached the maximum # of blocks, then flush the current
      // buffer and start over at the beginning.

      if (init_blkcnt < blocks_per_io)
        isodirP = (ISO9660DIRECTORYREC *)((UBYTE *)isodirP + rem_sector_len);
      else
        {
        (callback)(iobufP->data, init_blkcnt, log_flag);
        isodirP = (ISO9660DIRECTORYREC *)iobufP->data;
        written_blkcnt += init_blkcnt;
        init_blkcnt = 0;
        }

      rem_sector_len = SECTOR_MODE1_BLKLEN;
      }

    // Build the directory entry.

    isodirP->record_length = record_length;
    isodirP->ext_attr_blkcnt = 0;
    
    if (long_filenames_flag)
      {
      ConvertLongToLEBE (filenodeP->joliet_lba, (ULONG *)isodirP->lba);
      ConvertLongToLEBE (filenodeP->joliet_size, isodirP->data_length);
      }
    else
      {
      ConvertLongToLEBE (filenodeP->iso9660_lba, (ULONG *)isodirP->lba);
      ConvertLongToLEBE (filenodeP->iso9660_size, isodirP->data_length);
      }

    ISO9660ConvertDate (
      (gCDFSFileDateValid ? &gCDFSFileDate : &filenodeP->modified_datetime), &isodirP->date);
    
    isodirP->flags =
      ((filenodeP->dir_flag ? ISO9660_M_DIRECTORY : 0) |
       (filenodeP->hidden_flag ? ISO9660_M_HIDDEN_FILE : 0));
    isodirP->file_unit_size = 0;
    isodirP->interleave = 0;
    ConvertWordToLEBE (1, isodirP->volume_seq_number);
    isodirP->name_length = filnam_len;

    // Copy the filename and optionally append an extension and version number.
    // If necessary, add a padding byte at the end of the record.

    char *copyP = &isodirP->name[0];

    if (long_filenames_flag)
      {
      int outlen = ConvertStringToUnicode (copyP, nameP, strlen(nameP), TRUE); copyP += (outlen * 2);
      if (add_extension_flag) {ConvertStringToUnicode (copyP, ".", 1, TRUE); copyP += 2;}
      if (add_version_flag) {ConvertStringToUnicode (copyP, ";1", 2, TRUE); copyP += 4;}
      }
    else
      {
      memcpy (copyP, nameP, strlen(nameP)); copyP += strlen(nameP);
      if (add_extension_flag) {memcpy (copyP, ".", 1); copyP += 1;}
      if (add_version_flag) {memcpy (copyP, ";1", 2); copyP += 2;}
      }

    if (EVEN (filnam_len)) *copyP = 0;

    // Increment the buffer pointer.

    isodirP = (ISO9660DIRECTORYREC *)((UBYTE *)isodirP + record_length);

    // Decrement the amount of space left in this sector.

    rem_sector_len = rem_sector_len - record_length;
    }
  ABSQ_POOL

  // Flush the final buffer.

  CH_FILL (0, rem_sector_len, isodirP); init_blkcnt++;
  (callback)(iobufP->data, init_blkcnt, log_flag);
  written_blkcnt += init_blkcnt;

  // Make sure that we wrote the correct number of blocks.

  if (written_blkcnt != dir_blkcnt) SignalException (E_BugCheck);
}

//
// Initialize a primary volume descriptor.
//

void ISO9660InitPrimaryVolumeDesc(
  ISO9660VOLUMEDESC *volumedescP, CDFSOPTIONS *cdfs_optionsP, SYSDATETIME *datetimeP,
  ULONG volume_blkcnt, UWORD pathtable_len, SLONG le_pathtable_lba, SLONG be_pathtable_lba,
  SLONG rootdir_lba, UWORD rootdir_blkcnt, BOOL long_filenames_flag)
{
  ISO9660PRIMARYVOLUMEDESC *primarydescP = &volumedescP->primarydesc;

  // Initialize to zeros to start.

  CH_FILL (0, sizeof(ISO9660VOLUMEDESC), volumedescP);

  // Initialize the header fields.

  volumedescP->type = ISO9660_VD_PRIMARY;
  memcpy (volumedescP->id, ISO9660_VOLUME_DESC_ID, strlen(ISO9660_VOLUME_DESC_ID));
  volumedescP->version = 1;

  // Initialize the ID string fields.

  MemCopy (
    primarydescP->system_id,
    sizeof(primarydescP->system_id),
    cdfs_optionsP->iso9660_system_id,
    strlen(cdfs_optionsP->iso9660_system_id),
    ' ');

  MemCopy (
    primarydescP->volume_id,
    sizeof(primarydescP->volume_id),
    cdfs_optionsP->iso9660_volume_id,
    strlen(cdfs_optionsP->iso9660_volume_id),
    ' ');

  // Set the Joliet character set escape sequence?

  if (long_filenames_flag)
    {
    primarydescP->escape_sequence[0] = 0x25;
    primarydescP->escape_sequence[1] = 0x2F;
    primarydescP->escape_sequence[2] = 0x40;
    }

  // Set the volume information.

  ConvertLongToLEBE (volume_blkcnt, primarydescP->volume_blkcnt);
  ConvertWordToLEBE (1, primarydescP->volume_set_count);
  ConvertWordToLEBE (1, primarydescP->volume_sequence_number);
  ConvertWordToLEBE (SECTOR_MODE1_BLKLEN, primarydescP->logical_blklen);

  // Initialize the pathtable information.

  ConvertLongToLEBE (pathtable_len, primarydescP->pathtable_len);

  ConvertLongToLE (le_pathtable_lba, (ULONG *)&primarydescP->le_pathtable_lba);
  ConvertLongToBE (be_pathtable_lba, (ULONG *)&primarydescP->be_pathtable_lba);

  // Initialize the root directory record.

  ISO9660InitRootDirectory (
    &primarydescP->root_directory, datetimeP, rootdir_lba, rootdir_blkcnt);

  // Set the file structure version.

  primarydescP->file_structure_version = 1;

  // Initialize all text fields.

  MemCopy (
    primarydescP->volume_set_name,
    sizeof(primarydescP->volume_set_name),
    cdfs_optionsP->iso9660_volume_set_name,
    strlen(cdfs_optionsP->iso9660_volume_set_name),
    ' ');

  MemCopy (
    primarydescP->publisher_name,
    sizeof(primarydescP->publisher_name),
    cdfs_optionsP->iso9660_publisher_name,
    strlen(cdfs_optionsP->iso9660_publisher_name),
    ' ');

  MemCopy (
    primarydescP->preparer_name,
    sizeof(primarydescP->preparer_name),
    cdfs_optionsP->iso9660_preparer_name,
    strlen(cdfs_optionsP->iso9660_preparer_name),
    ' ');
  
  MemCopy (
    primarydescP->application_name,
    sizeof(primarydescP->application_name),
    cdfs_optionsP->iso9660_application_name,
    strlen(cdfs_optionsP->iso9660_application_name),
    ' ');

  MemCopy (
    primarydescP->copyright_file,
    sizeof(primarydescP->copyright_file),
    cdfs_optionsP->iso9660_copyright_file,
    strlen(cdfs_optionsP->iso9660_copyright_file),
    ' ');

  MemCopy (
    primarydescP->abstract_file,
    sizeof(primarydescP->abstract_file),
    cdfs_optionsP->iso9660_abstract_file,
    strlen(cdfs_optionsP->iso9660_abstract_file),
    ' ');

  MemCopy (
    primarydescP->bibliographic_file,
    sizeof(primarydescP->bibliographic_file),
    cdfs_optionsP->iso9660_bibliographic_file,
    strlen(cdfs_optionsP->iso9660_bibliographic_file),
    ' ');

  // Initialize the dates.

  if (! cdfs_optionsP->creation_date_valid)
    sprintf (primarydescP->creation_date, dateformat_str,
      datetimeP->date.year, datetimeP->date.month, datetimeP->date.day,
      datetimeP->time.hour, datetimeP->time.minute, datetimeP->time.second);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->creation_date;

    sprintf (primarydescP->creation_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->modification_date_valid)
    sprintf (primarydescP->modification_date, dateformat_str,
      datetimeP->date.year, datetimeP->date.month, datetimeP->date.day,
      datetimeP->time.hour, datetimeP->time.minute, datetimeP->time.second);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->modification_date;

    sprintf (primarydescP->modification_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->expiration_date_valid)
    memcpy (primarydescP->expiration_date, "0000000000000000", 16);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->expiration_date;

    sprintf (primarydescP->expiration_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->effective_date_valid)
    memcpy (primarydescP->effective_date, "0000000000000000", 16);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->effective_date;

    sprintf (primarydescP->effective_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }
}

//
// Initialize a Joliet supplemental volume descriptor
//

void ISO9660InitSupplementalVolumeDesc(
  ISO9660VOLUMEDESC *volumedescP, CDFSOPTIONS *cdfs_optionsP, SYSDATETIME *datetimeP,
  ULONG volume_blkcnt, UWORD pathtable_len, SLONG le_pathtable_lba, SLONG be_pathtable_lba,
  SLONG rootdir_lba, UWORD rootdir_blkcnt)
{
  char *volume_id, *system_id;
  char *volume_set_name, *publisher_name, *preparer_name, *application_name;
  char *copyright_file, *abstract_file, *bibliographic_file;

  ISO9660PRIMARYVOLUMEDESC *primarydescP = &volumedescP->primarydesc;

  // Initialize to zeros to start.

  CH_FILL (0, sizeof(ISO9660VOLUMEDESC), volumedescP);

  // Initialize the header fields.

  volumedescP->type = ISO9660_VD_SUPPLEMENTAL;
  memcpy (volumedescP->id, ISO9660_VOLUME_DESC_ID, strlen(ISO9660_VOLUME_DESC_ID));
  volumedescP->version = 1;

  // Set the Joliet character set escape sequence.

  primarydescP->escape_sequence[0] = 0x25;
  primarydescP->escape_sequence[1] = 0x2F;
  primarydescP->escape_sequence[2] = 0x40;

  // Set the volume information.

  ConvertLongToLEBE (volume_blkcnt, primarydescP->volume_blkcnt);
  ConvertWordToLEBE (1, primarydescP->volume_set_count);
  ConvertWordToLEBE (1, primarydescP->volume_sequence_number);
  ConvertWordToLEBE (SECTOR_MODE1_BLKLEN, primarydescP->logical_blklen);

  // Initialize the pathtable information.

  ConvertLongToLEBE (pathtable_len, primarydescP->pathtable_len);
  ConvertLongToLE (le_pathtable_lba, (ULONG *)&primarydescP->le_pathtable_lba);
  ConvertLongToBE (be_pathtable_lba, (ULONG *)&primarydescP->be_pathtable_lba);

  // Initialize the root directory record.

  ISO9660InitRootDirectory (
    &primarydescP->root_directory, datetimeP, rootdir_lba, rootdir_blkcnt);

  // Set the file structure version.

  primarydescP->file_structure_version = 1;

  // Initialize all text fields.

  if (cdfs_optionsP->joliet_text_valid)
    {
    system_id = cdfs_optionsP->joliet_system_id;
    volume_id = cdfs_optionsP->joliet_volume_id;
    volume_set_name = cdfs_optionsP->joliet_volume_set_name;
    publisher_name = cdfs_optionsP->joliet_publisher_name;
    preparer_name = cdfs_optionsP->joliet_preparer_name;
    application_name = cdfs_optionsP->joliet_application_name;
    copyright_file = cdfs_optionsP->joliet_copyright_file;
    abstract_file = cdfs_optionsP->joliet_abstract_file;
    bibliographic_file = cdfs_optionsP->joliet_bibliographic_file;
    }
  else
    {
    system_id = cdfs_optionsP->iso9660_system_id;
    volume_id = cdfs_optionsP->iso9660_volume_id;
    volume_set_name = cdfs_optionsP->iso9660_volume_set_name;
    publisher_name = cdfs_optionsP->iso9660_publisher_name;
    preparer_name = cdfs_optionsP->iso9660_preparer_name;
    application_name = cdfs_optionsP->iso9660_application_name;
    copyright_file = cdfs_optionsP->iso9660_copyright_file;
    abstract_file = cdfs_optionsP->iso9660_abstract_file;
    bibliographic_file = cdfs_optionsP->iso9660_bibliographic_file;
    }

  ConvertStringToUnicode (
    primarydescP->system_id, sizeof(primarydescP->system_id),
    system_id, strlen(system_id), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->volume_id, sizeof(primarydescP->volume_id),
    volume_id, strlen(volume_id), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->volume_set_name, sizeof(primarydescP->volume_set_name),
    volume_set_name, strlen(volume_set_name), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->publisher_name, sizeof(primarydescP->publisher_name),
    publisher_name, strlen(publisher_name), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->preparer_name, sizeof(primarydescP->preparer_name),
    preparer_name, strlen(preparer_name), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->application_name, sizeof(primarydescP->application_name),
    application_name, strlen(application_name), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->copyright_file, sizeof(primarydescP->copyright_file),
    copyright_file, strlen(copyright_file), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->abstract_file, sizeof(primarydescP->abstract_file),
    abstract_file, strlen(abstract_file), ' ', TRUE);

  ConvertStringToUnicode (
    primarydescP->bibliographic_file, sizeof(primarydescP->bibliographic_file),
    bibliographic_file, strlen(bibliographic_file), ' ', TRUE);

  // Initialize the dates.

  if (! cdfs_optionsP->creation_date_valid)
    sprintf (primarydescP->creation_date, dateformat_str,
      datetimeP->date.year, datetimeP->date.month, datetimeP->date.day,
      datetimeP->time.hour, datetimeP->time.minute, datetimeP->time.second);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->creation_date;

    sprintf (primarydescP->creation_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->modification_date_valid)
    sprintf (primarydescP->modification_date, dateformat_str,
      datetimeP->date.year, datetimeP->date.month, datetimeP->date.day,
      datetimeP->time.hour, datetimeP->time.minute, datetimeP->time.second);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->modification_date;

    sprintf (primarydescP->modification_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->expiration_date_valid)
    memcpy (primarydescP->expiration_date, "0000000000000000", 16);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->expiration_date;

    sprintf (primarydescP->expiration_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }

  if (! cdfs_optionsP->effective_date_valid)
    memcpy (primarydescP->effective_date, "0000000000000000", 16);
  else
    {
    SYSDATETIME *dateP = &cdfs_optionsP->effective_date;

    sprintf (primarydescP->effective_date, dateformat_str,
      dateP->date.year, dateP->date.month, dateP->date.day,
      dateP->time.hour, dateP->time.minute, dateP->time.second);
    }
}

//
// Initialize a terminating volume descriptor.
//

void ISO9660InitTermVolumeDesc(ISO9660VOLUMEDESC *volumedescP)
{
  // Initialize to zeros to start.

  CH_FILL (0, sizeof(ISO9660VOLUMEDESC), volumedescP);

  // Initialze the header fields.

  volumedescP->type = ISO9660_VD_END;
  memcpy (volumedescP->id, ISO9660_VOLUME_DESC_ID, strlen(ISO9660_VOLUME_DESC_ID));
  volumedescP->version = 1;
}

//
// Initialize an "El Torito" boot record volume descriptor.
//

void ISO9660InitBootRecordVolumeDesc(ISO9660VOLUMEDESC *volumedescP, ULONG boot_catalog_lba)
{
  ISO9660BOOTRECORDVOLUMEDESC *bootdescP = &volumedescP->bootrecorddesc;

  // Initialize to zeros to start.

  CH_FILL (0, sizeof(ISO9660VOLUMEDESC), volumedescP);

  // Initialize the header fields.

  volumedescP->type = 0;
  memcpy (volumedescP->id, ISO9660_VOLUME_DESC_ID, strlen(ISO9660_VOLUME_DESC_ID));
  volumedescP->version = 1;

  // Initialize the boot record descriptor fields.

  memcpy (bootdescP->boot_id, ELTORITO_BOOTSYSTEM_ID, strlen(ELTORITO_BOOTSYSTEM_ID));
  bootdescP->boot_catalog_lba = boot_catalog_lba;
}

//
// Build an "El Torito" boot catalog.
//

void ISO9660BuildBootCatalog(
  BOOTCATALOG *bootcatalog_vec, const char *developer, UBYTE media_type,
  UWORD load_segment, UWORD load_sector_count, ULONG load_lba)
{
  BOOTCATALOG *bootcatalogP = bootcatalog_vec;
  
  CH_FILL (0, 2048, bootcatalog_vec);

  // Initialize the validation entry.

  bootcatalogP->ve.header = 1;
  bootcatalogP->ve.platform = ELTORITO_PLATFORM_80X86;

  MemCopy (
    bootcatalogP->ve.developer, sizeof(bootcatalogP->ve.developer),
    developer, strlen(developer), 0);

  bootcatalogP->ve.keybyte1 = 0x55;
  bootcatalogP->ve.keybyte2 = 0xAA;
  bootcatalogP->ve.checksum = (- Checksum16(&bootcatalogP->ve, sizeof(BOOTCATALOG_VE)));
  bootcatalogP++;

  // Initialize the initial/default entry.

  bootcatalogP->ide.boot_indicator = ELTORITO_BOOTABLE;
  bootcatalogP->ide.media_type = media_type;
  bootcatalogP->ide.system_type = 0;
  bootcatalogP->ide.load_lba = load_lba;

  if (media_type == ELTORITO_MEDIA_CUSTOM)
    {
    bootcatalogP->ide.load_segment =
      ((load_segment == ELTORITO_DEF_LOAD_SEGMENT) ? 0 : load_segment);
    bootcatalogP->ide.load_sector_count = load_sector_count;
    }
  else
    {
    bootcatalogP->ide.load_segment = 0;
    bootcatalogP->ide.load_sector_count = 1;
    }

  bootcatalogP++;
}

// Restore previous packing alignment.

#pragma pack(pop)
