summaryrefslogtreecommitdiffstats
path: root/private/windows/diamond/ddump.c
diff options
context:
space:
mode:
authorAdam <you@example.com>2020-05-17 05:51:50 +0200
committerAdam <you@example.com>2020-05-17 05:51:50 +0200
commite611b132f9b8abe35b362e5870b74bce94a1e58e (patch)
treea5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/windows/diamond/ddump.c
downloadNT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip
Diffstat (limited to 'private/windows/diamond/ddump.c')
-rw-r--r--private/windows/diamond/ddump.c787
1 files changed, 787 insertions, 0 deletions
diff --git a/private/windows/diamond/ddump.c b/private/windows/diamond/ddump.c
new file mode 100644
index 000000000..ff6a8bc95
--- /dev/null
+++ b/private/windows/diamond/ddump.c
@@ -0,0 +1,787 @@
+/*** ddump.c - Diamond cabinet file dumper
+ *
+ * Microsoft Confidential
+ * Copyright (C) Microsoft Corporation 1994
+ * All Rights Reserved.
+ *
+ * Author:
+ * Benjamin W. Slivka
+ *
+ * History:
+ * 16-Mar-1994 bens Initial version (started with extract.c)
+ * 28-Mar-1994 bens Update for version 1.03.
+ * 18-May-1994 bens Correct order of cabinet file name, disk name
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <malloc.h>
+#include <fcntl.h>
+#include <sys\types.h>
+#include <sys\stat.h>
+#include <io.h>
+#include <errno.h>
+#include <direct.h>
+
+#include "types.h"
+#include "asrt.h"
+#include "error.h"
+#include "mem.h"
+#include "message.h"
+
+#include "filelist.h"
+#include "fileutil.h"
+
+#include "chuck\types.h"
+#include "chuck\cabinet.h"
+
+#include "ddump.msg"
+
+//** Constants
+
+#define cbMAX_LINE 256 // Maximum output line length
+
+
+//** Types
+
+typedef enum {
+ actBAD, // Invalid action
+ actHELP, // Show help
+ actFILE, // Extract from single file cabinet(s)
+ actCABINET, // Extract from multiple-file cabinet(s)
+} ACTION; /* act */
+
+
+#ifdef ASSERT
+#define sigSESSION MAKESIG('S','E','S','S') // SESSION signature
+#define AssertSess(psess) AssertStructure(psess,sigSESSION);
+#else // !ASSERT
+#define AssertSess(psess)
+#endif // !ASSERT
+
+typedef struct {
+#ifdef ASSERT
+ SIGNATURE sig; // structure signature sigSESSION
+#endif
+ ACTION act; // Action to perform
+ HFILELIST hflist; // List of files specified on cmd line
+ BOOL fNoLineFeed; // TRUE if last printf did not have \n
+ BOOL fVerbose; // TRUE ==> Dump everything
+ long cFiles; // Total files processed
+ PERROR perr; // Pass through FDI
+ char achMsg[cbMAX_LINE*2]; // Message formatting buffer
+ char achFile[cbFILE_NAME_MAX]; // Current filename being extracted
+
+ char achPrevDisk[255];
+ char achPrevCabFile[255];
+ char achNextDisk[255];
+ char achNextCabFile[255];
+
+ CFHEADER cfheader;
+ CFRESERVE cfreserve;
+ char *pbCFHeaderReserve; // Reserved data for cabinet
+ CFFOLDER *pcffolder; // CFFOLDER + reserved buffer
+ CFDATA *pcfdata; // CFDATA + reserved buffer
+
+ UINT cbCFHeaderReserve; // Size of CFHEADER RESERVE
+ UINT cbCFFolderPlusReserve; // Size of CFFOLDER + folder RESERVE
+ UINT cbCFDataPlusReserve; // Size of CFDATA + data RESERVE
+
+} SESSION; /* sess */
+typedef SESSION *PSESSION; /* psess */
+
+
+//** Function Prototypes
+
+FNASSERTFAILURE(fnafReport);
+
+HFILESPEC addFileSpec(PSESSION psess,char *pszArg,PERROR perr);
+BOOL doCabinet(PSESSION psess,PERROR perr);
+BOOL parseCommandLine(PSESSION psess,int cArg,char *apszArg[],PERROR perr);
+void printError(PSESSION psess,PERROR perr);
+void pszFromMSDOSTime(char *psz,int cb,WORD date,WORD time);
+
+BOOL doCFHeader(PSESSION psess,int fh,char *pszFile,PERROR perr);
+BOOL doCFFolder(PSESSION psess,int fh,int iFolder,PERROR perr);
+BOOL doCFFile(PSESSION psess,int fh,int iFile,PERROR perr);
+ULONG doCFData(PSESSION psess,int fh,int iData,ULONG uoffFolder,PERROR perr);
+
+BOOL readPSZ(int fh, char *pb, int cb);
+
+
+//** Functions
+
+/*** main - DDUMP main program
+ *
+ * NOTE: We're sloppy, and don't free resources allocated by
+ * functions we call, on the assumption that program exit
+ * will clean up memory and file handles for us.
+ */
+int __cdecl main(int cArg, char *apszArg[])
+{
+ char ach[cbMSG_MAX];
+ ERROR err;
+ PSESSION psess;
+
+ AssertRegisterFunc(fnafReport); // Register assertion reporter
+ ErrClear(&err); // No error
+ err.pszFile = NULL; // No file being processed, yet
+
+ //** Initialize session
+ psess = MemAlloc(sizeof(SESSION));
+ if (!psess) {
+ ErrSet(&err,pszDDERR_NO_SESSION);
+ printError(psess,&err);
+ exit(1);
+ }
+ SetAssertSignature((psess),sigSESSION);
+ psess->hflist = NULL;
+ psess->fNoLineFeed = FALSE; // No print status, yet
+ psess->fVerbose = FALSE; // Default to non-verbose output
+ psess->cFiles = 0; // No files, yet
+ psess->pbCFHeaderReserve = NULL; // No per-cabinet reserved data, yet
+ psess->pcffolder = NULL; // No buffer for CFFOLDER, yet
+ psess->pcfdata = NULL; // No buffer for CFDATA, yet
+
+ //** Print Extract banner
+ MsgSet(ach,pszBANNER,"%s",pszDDUMP_VERSION);
+ printf(ach);
+
+ //** Parse command line
+ if (!parseCommandLine(psess,cArg,apszArg,&err)) {
+ printError(psess,&err);
+ return 1;
+ }
+
+ //** Quick out if command line help is requested
+ if (psess->act == actHELP) { // Do help if any args, for now
+ printf("\n"); // Separate banner from help
+ printf(pszCMD_LINE_HELP);
+ return 0;
+ }
+
+ //** Have some work to do -- go do it
+ if (!doCabinet(psess,&err)) {
+ printError(psess,&err);
+ return 1;
+ }
+
+ //** Free resources
+ AssertSess(psess);
+ ClearAssertSignature((psess));
+ MemFree(psess);
+
+ //** Success
+ return 0;
+} /* main */
+
+
+/*** doCabinet - Dump contents of cabinet
+ *
+ * Entry:
+ * psess - Description of operation to perform
+ * perr - ERROR structure
+ *
+ * Exit-Success:
+ * Returns TRUE; directory displayed
+ *
+ * Exit-Failure:
+ * Returns FALSE; perr filled in with details.
+ */
+BOOL doCabinet(PSESSION psess, PERROR perr)
+{
+ UINT cCFData;
+ int fh; // File handle of cabinet
+ HFILESPEC hfspec;
+ UINT i;
+ ULONG off;
+ ULONG offCFDataForFirstFolder;
+ char *pszCabinet; // Cabinet filespec
+ ULONG uoff;
+ ULONG uoffFolder;
+
+ //** Process a cabinet
+ hfspec = FLFirstFile(psess->hflist);
+ Assert(hfspec != NULL); // Must have at least one file
+ pszCabinet = FLGetSource(hfspec);
+ Assert(pszCabinet!=NULL);
+ Assert(*pszCabinet);
+
+ //** Open cabinet file
+ if (-1 == (fh = _open(pszCabinet,_O_BINARY | _O_RDONLY))) {
+ ErrSet(perr,pszDDERR_OPEN_FAILED,"%s",pszCabinet);
+ return FALSE;
+ }
+
+ printf("Cabinet File: %s\n",pszCabinet);
+
+ //** Do header
+ if (!doCFHeader(psess,fh,pszCabinet,perr)) {
+ return FALSE;
+ }
+
+ //** Do folders
+ cCFData = 0;
+ for (i=0; i<psess->cfheader.cFolders; i++) {
+ if (!doCFFolder(psess,fh,i,perr)) {
+ return FALSE;
+ }
+ cCFData += psess->pcffolder->cCFData; // Count total CFData's
+ if (i == 0) {
+ offCFDataForFirstFolder = psess->pcffolder->coffCabStart;
+ }
+ }
+
+ //** Make sure CFFILEs start where header said they did
+ off = _lseek(fh,0,SEEK_CUR);
+ if (psess->cfheader.coffFiles != off) {
+ printf("ERROR: Header says CFFILES start at %ld, really start at %ld!\n",
+ psess->cfheader.coffFiles, off);
+ }
+
+ //** Do files
+ for (i=0; i<psess->cfheader.cFiles; i++) {
+ if (!doCFFile(psess,fh,i,perr)) {
+ return FALSE;
+ }
+ }
+
+ //** Make sure CFDATAs start where first CFFOLDER said they did
+ off = _lseek(fh,0,SEEK_CUR);
+ if (offCFDataForFirstFolder != off) {
+ printf("ERROR: CFFOLDER says CFDATA start at %ld, really start at %ld!\n",
+ offCFDataForFirstFolder, off);
+ }
+
+ //** Only traverse through CFDATAs if requested (speeds up dumping on
+ // floppy disks, since we don't have to read the entire floppy!
+ if (psess->fVerbose) {
+ //** Do data blocks
+ uoffFolder = 0; // Track uncompressed offset
+ for (i=0; i<cCFData; i++) {
+ if (-1 == (uoff = doCFData(psess,fh,i,uoffFolder,perr))) {
+ return FALSE;
+ }
+ uoffFolder += uoff; // Advance uncompressed offset
+ }
+
+ //** Make sure cabinet file is expected length
+ off = _lseek(fh,0,SEEK_CUR);
+ if (psess->cfheader.cbCabinet != (long)off) {
+ printf("ERROR: Header says cabinet is %ld bytes, really is %ld!\n",
+ psess->cfheader.cbCabinet, off);
+ }
+ }
+
+ //** Success
+ return TRUE;
+} /* doCabinet() */
+
+
+/*** doCFHeader - display CFHEADER information
+ *
+ */
+BOOL doCFHeader(PSESSION psess, int fh, char *pszFile, PERROR perr)
+{
+ BOOL fPrev=FALSE;
+ BOOL fNext=FALSE;
+ BOOL fReserve=FALSE;
+
+ //** Read CFHEADER structure
+ if (sizeof(CFHEADER) != _read(fh,&psess->cfheader,sizeof(CFHEADER))) {
+ ErrSet(perr,pszDDERR_NOT_A_CABINET,"%s",pszFile);
+ return FALSE;
+ }
+
+ //** Make sure this is a cabinet file
+ if (psess->cfheader.sig != sigCFHEADER) {
+ ErrSet(perr,pszDDERR_NOT_A_CABINET,"%s",pszFile);
+ return FALSE; // Signature does not match
+ }
+
+ //** Make sure we know this version number
+ if (psess->cfheader.version != verCF) {
+ ErrSet(perr,pszDDERR_UNKNOWN_CABINET_VERSION,"%s%d",
+ pszFile,psess->cfheader.version);
+ return FALSE;
+ }
+
+
+
+ psess->achMsg[0] = '\0';
+ if (psess->cfheader.flags & cfhdrPREV_CABINET) {
+ strcat(psess->achMsg,"Previous ");
+ fPrev = TRUE;
+ }
+
+ if (psess->cfheader.flags & cfhdrNEXT_CABINET) {
+ strcat(psess->achMsg,"Next ");
+ fNext = TRUE;
+ }
+
+ if (psess->cfheader.flags & cfhdrRESERVE_PRESENT) {
+ strcat(psess->achMsg,"Reserve ");
+ fReserve = TRUE;
+ }
+
+ printf("sig: %08lx\n" ,psess->cfheader.sig);
+ printf("csumHeader: %08lx\n" ,psess->cfheader.csumHeader);
+ printf("cbCabinet: %8ld\n" ,psess->cfheader.cbCabinet);
+ printf("csumFolders: %08lx\n" ,psess->cfheader.csumFolders);
+ printf("coffFiles: %8ld\n" ,psess->cfheader.coffFiles);
+ printf("csumFiles: %08lx\n" ,psess->cfheader.csumFiles);
+ printf("version: %04x\n" ,psess->cfheader.version);
+ printf("cFolders: %8d\n" ,psess->cfheader.cFolders);
+ printf("cFiles: %8d\n" ,psess->cfheader.cFiles);
+ printf("flags: %04x (%s)\n",psess->cfheader.flags,psess->achMsg);
+ printf("setID: %04x\n" ,psess->cfheader.setID);
+ printf("iCabinet: %05d\n" ,psess->cfheader.iCabinet);
+
+ //** Assume no CFRESERVE
+ psess->cbCFHeaderReserve = 0;
+ psess->cbCFFolderPlusReserve = sizeof(CFFOLDER);
+ psess->cbCFDataPlusReserve = sizeof(CFDATA);
+
+ //** Read CFRESERVE, if present
+ if (fReserve) {
+ //** Read CFRESERVE structure
+ if (sizeof(CFRESERVE) != _read(fh,&psess->cfreserve,sizeof(CFRESERVE))) {
+ return FALSE;
+ }
+
+ if (psess->pbCFHeaderReserve) {
+ MemFree(psess->pbCFHeaderReserve);
+ psess->pbCFHeaderReserve = NULL;
+ }
+ if (!(psess->pbCFHeaderReserve = MemAlloc(psess->cfreserve.cbCFHeader))) {
+ return FALSE;
+ }
+
+ psess->cbCFHeaderReserve = psess->cfreserve.cbCFHeader;
+ psess->cbCFFolderPlusReserve += psess->cfreserve.cbCFFolder;
+ psess->cbCFDataPlusReserve += psess->cfreserve.cbCFData;
+
+ printf(" cbCFHeader: %5d\n", psess->cfreserve.cbCFHeader);
+ printf(" cbCFFolder: %5d\n", psess->cfreserve.cbCFFolder);
+ printf(" cbCFData: %5d\n", psess->cfreserve.cbCFData);
+
+ //** Read reserved section
+ if (psess->cbCFHeaderReserve != (UINT)_read(fh,
+ psess->pbCFHeaderReserve,
+ psess->cbCFHeaderReserve)) {
+ return FALSE;
+ }
+//BUGBUG 17-Mar-1994 bens Print out reserved bytes with hex dump?
+ }
+
+ //** Allocate CFFOLDER and CFDATA buffers
+ if (psess->pcffolder) {
+ MemFree(psess->pcffolder);
+ psess->pcffolder = NULL;
+ }
+ if (!(psess->pcffolder = MemAlloc(psess->cbCFFolderPlusReserve))) {
+ return FALSE;
+ }
+
+ if (psess->pcfdata) {
+ MemFree(psess->pcfdata);
+ psess->pcfdata = NULL;
+ }
+ if (!(psess->pcfdata = MemAlloc(psess->cbCFDataPlusReserve))) {
+ return FALSE;
+ }
+
+ //** Read prev/next disk/cab strings
+ psess->achPrevCabFile[0] = '\0';
+ psess->achPrevDisk[0] = '\0';
+ psess->achNextCabFile[0] = '\0';
+ psess->achNextDisk[0] = '\0';
+
+ if (fPrev) {
+ if (!readPSZ(fh,psess->achPrevCabFile,sizeof(psess->achPrevCabFile))) {
+ return FALSE;
+ }
+ if (!readPSZ(fh,psess->achPrevDisk,sizeof(psess->achPrevDisk))) {
+ return FALSE;
+ }
+ printf("PrevCabFile: %s\n",psess->achPrevCabFile);
+ printf("PrevDisk: %s\n",psess->achPrevDisk);
+ }
+
+ if (fNext) {
+ if (!readPSZ(fh,psess->achNextCabFile,sizeof(psess->achNextCabFile))) {
+ return FALSE;
+ }
+ if (!readPSZ(fh,psess->achNextDisk,sizeof(psess->achNextDisk))) {
+ return FALSE;
+ }
+ printf("NextCabFile: %s\n",psess->achNextCabFile);
+ printf("NextDisk: '%s'\n",psess->achNextDisk);
+ }
+
+ //** Blank line to separate header from first folder
+ printf("\n");
+ return TRUE;
+}
+
+
+/*** doCFFolderr - display CFFOLDER information
+ *
+ */
+BOOL doCFFolder(PSESSION psess, int fh, int iFolder, PERROR perr)
+{
+ char ach[256];
+ static BOOL fFirstTime=TRUE;
+ char *pszCompression;
+
+ //** Read CFFOLDER and reserved data
+ if (psess->cbCFFolderPlusReserve != (UINT)_read(fh,
+ psess->pcffolder,
+ psess->cbCFFolderPlusReserve)) {
+ return FALSE;
+ }
+
+ switch (CompressionTypeFromTCOMP(psess->pcffolder->typeCompress)) {
+ case tcompTYPE_NONE:
+ pszCompression = "None";
+ break;
+
+ case tcompTYPE_MSZIP:
+ pszCompression = "MSZIP";
+ break;
+
+ case tcompTYPE_QUANTUM:
+ sprintf(ach,"Quantum Level=%d Memory=%d",
+ CompressionLevelFromTCOMP(psess->pcffolder->typeCompress),
+ CompressionMemoryFromTCOMP(psess->pcffolder->typeCompress));
+ pszCompression = ach;
+ break;
+
+ default:
+ sprintf(psess->achMsg,"Unknown compression: %04x",
+ psess->pcffolder->typeCompress);
+ pszCompression = psess->achMsg;
+ break;
+ }
+
+ if (fFirstTime) {
+ printf("iFolder offCabinet cData Compression\n");
+ fFirstTime = FALSE;
+ }
+ printf(" %3d %8ld %5d %s\n",
+ iFolder,
+ psess->pcffolder->coffCabStart,
+ psess->pcffolder->cCFData,
+ pszCompression);
+
+//BUGBUG 17-Mar-1994 bens Dump reserved data in hex dump form?
+ return TRUE;
+}
+
+
+/*** doCFFile - display CFFILE information
+ *
+ */
+BOOL doCFFile(PSESSION psess, int fh, int iFile, PERROR perr)
+{
+ CFFILE cffile;
+ static BOOL fFirstTime=TRUE;
+
+ psess->cFiles++; // Count file
+
+ //** Read CFFILE structure
+ if (sizeof(CFFILE) != _read(fh,&cffile,sizeof(cffile))) {
+ return FALSE;
+ }
+
+ if (!readPSZ(fh,psess->achFile,sizeof(psess->achFile))) {
+ return FALSE;
+ }
+
+ //** Format date and time
+ pszFromMSDOSTime(psess->achMsg,
+ sizeof(psess->achMsg),
+ cffile.date,
+ cffile.time);
+
+ if (fFirstTime) {
+ printf("\n");
+ printf("iFile cbFile offFolder iFolder attr date time name\n");
+ fFirstTime = FALSE;
+ }
+ printf("%5d %8ld %9ld %7d %04x %s %s\n",
+ iFile,
+ cffile.cbFile,
+ cffile.uoffFolderStart,
+ cffile.iFolder,
+ cffile.attribs,
+ psess->achMsg,
+ psess->achFile
+ );
+
+ return TRUE;
+}
+
+
+/*** doCFData - display CFDATA information
+ *
+ */
+ULONG doCFData(PSESSION psess, int fh, int iData, ULONG uoffFolder, PERROR perr)
+{
+ static BOOL fFirstTime=TRUE;
+
+ //** Read CFFOLDER and reserved data
+ if (psess->cbCFDataPlusReserve != (UINT)_read(fh,
+ psess->pcfdata,
+ psess->cbCFDataPlusReserve)) {
+ return FALSE;
+ }
+
+ if (fFirstTime) {
+ printf("\n");
+ printf("iData csumData offFolder cbData cbUncomp\n");
+ fFirstTime = FALSE;
+ }
+ printf("%5d %08lx %9ld %6u %6u\n",
+ iData,
+ psess->pcfdata->csum,
+ uoffFolder,
+ psess->pcfdata->cbData,
+ psess->pcfdata->cbUncomp);
+
+ //** Skip over actual data
+ _lseek(fh,psess->pcfdata->cbData,SEEK_CUR);
+
+ return (ULONG)psess->pcfdata->cbUncomp;
+} /* doCFData() */
+
+
+/*** readPSZ - read ASCIIZ string from file
+ *
+ */
+BOOL readPSZ(int fh, char *pb, int cb)
+{
+ char chLast;
+ int cbValue;
+ int cbRead;
+ long pos;
+
+ pos = _lseek(fh,0,SEEK_CUR); // Save current position
+
+ //** Read in enough to get longest possible value
+ cbRead = _read(fh,pb,cb);
+//BUGBUG 17-Mar-1994 bens Need to check for error/eof from read
+ chLast = pb[cb-1]; // Save last character
+ pb[cb-1] = '\0'; // Ensure terminated
+ cbValue = strlen(pb); // Get string length
+ if ( ((cbValue+1) >= cb) && (chLast != '\0')) {
+ //** String filled up buffer and was not null terminated in
+ // file, so something must be wrong.
+ return FALSE;
+ }
+
+ _lseek(fh,pos+cbValue+1,SEEK_SET); // Position to just past string
+ return TRUE;
+}
+
+
+/*** parseCommandLine - Parse the command line arguments
+ *
+ * Entry:
+ * cArg - Count of arguments, including program name
+ * apszArg - Array of argument strings
+ * perr - ERROR structure
+ *
+ * Exit-Success:
+ * Returns TRUE; psess filled in.
+ *
+ * Exit-Failure:
+ * Returns actBAD; perr filled in with error.
+ */
+BOOL parseCommandLine(PSESSION psess, int cArg, char *apszArg[], PERROR perr)
+{
+ int cFile=0; // Count of non-directive file names seen
+ char ch; // Switch value
+ int i;
+
+ AssertSess(psess);
+ psess->act = actBAD; // We don't know what we are doing, yet
+
+ //** Parse args, skipping program name
+ for (i=1; i<cArg; i++) {
+ if ((apszArg[i][0] == chSWITCH1) ||
+ (apszArg[i][0] == chSWITCH2) ) {
+
+ ch = toupper(apszArg[i][1]); // Switch value
+ switch (ch) {
+ case chSWITCH_VERBOSE:
+ if (apszArg[i][2] != '\0') {
+ ErrSet(perr,pszDDERR_BAD_SWITCH,"%s",apszArg[i]);
+ return FALSE;
+ }
+ psess->fVerbose = TRUE; // Show everything
+ break;
+
+ case chSWITCH_HELP:
+ if (apszArg[i][2] != '\0') {
+ ErrSet(perr,pszDDERR_BAD_SWITCH,"%s",apszArg[i]);
+ return FALSE;
+ }
+ psess->act = actHELP; // Show help
+ return TRUE;
+
+ default:
+ ErrSet(perr,pszDDERR_BAD_SWITCH,"%s",apszArg[i]);
+ return FALSE;
+ break;
+ }
+ }
+ else {
+ //** We have a file name; add it to our list
+ if (addFileSpec(psess,apszArg[i],perr) == NULL) {
+ ErrSet(perr,pszDDERR_COULD_NOT_ADD_FILE,"%s",apszArg[i]);
+ return FALSE;
+ }
+ cFile++; // Count files
+ }
+ }
+
+ //** Make sure we got at least one filespec
+ if (cFile == 0) {
+ psess->act = actHELP; // Show help
+ }
+
+ //** Success
+ return TRUE;
+}
+
+
+/*** addFileSpec - Add filename to session list
+ *
+ * Entry:
+ * psess - Session to update
+ * pszArg - File name to add
+ * perr - ERROR structure
+ *
+ * Exit-Success:
+ * Returns HFILESPEC; psess updated.
+ *
+ * Exit-Failure:
+ * Returns NULL; perr filled in with error.
+ */
+HFILESPEC addFileSpec(PSESSION psess, char *pszArg, PERROR perr)
+{
+ HFILESPEC hfspec;
+
+ AssertSess(psess);
+ //** Make sure a list exists
+ if (psess->hflist == NULL) {
+ if (!(psess->hflist = FLCreateList(perr))) {
+ return FALSE;
+ }
+ }
+
+ //** Add file to list
+ if (!(hfspec = FLAddFile(psess->hflist, pszArg, NULL, perr))) {
+ return NULL;
+ }
+
+ //** Success
+ return hfspec;
+} /* addFileSpec() */
+
+
+#ifdef ASSERT
+/*** fnafReport - Report assertion failure
+ *
+ * NOTE: See asrt.h for entry/exit conditions.
+ */
+FNASSERTFAILURE(fnafReport)
+{
+ printf("\n%s:(%d) Assertion Failed: %s\n",pszFile,iLine,pszMsg);
+ exit(1);
+}
+#endif // ASSERT
+
+
+/*** printError - Display error on stdout
+ *
+ * Entry
+ * perr - ERROR structure to print
+ *
+ * Exit-Success
+ * Writes error message to stdout.
+ */
+void printError(PSESSION psess, PERROR perr)
+{
+ //** Make sure error starts on a new line
+ if (psess->fNoLineFeed) {
+ printf("\n");
+ psess->fNoLineFeed = FALSE;
+ }
+
+ //** General error
+ printf("%s: %s\n",pszDDERR_ERROR,perr->ach);
+}
+
+
+/*** pszFromMSDOSTime - Convert MS-DOS file date/time to string
+ *
+ * Entry:
+ * psz - Buffer to receive formatted date/time
+ * cb - Length of psz buffer
+ * date - MS-DOS FAT file system date format (see below)
+ * time - MS-DOS FAT file system time format (see below)
+ *
+ * Exit:
+ * *ptm filled in
+ *
+ * NOTE: This is the interpretation of the MS-DOS date/time values:
+ *
+ * Time Bits cBits Meaning
+ * --------- ----- ----------------------------------------
+ * 0 - 4 5 Number of two-second increments (0 - 29)
+ * 5 - 10 6 Minutes (0 - 59)
+ * 11 - 15 5 Hours (0 - 23)
+ *
+ * Date Bits cBits Meaning
+ * --------- ----- ----------------------------------------
+ * 0 - 4 5 Day (1 - 31)
+ * 5 - 8 4 Month (1 - 12)
+ * 9 - 15 7 Year since 1980 (for example, 1994 is stored as 14)
+ */
+void pszFromMSDOSTime(char *psz, int cb, WORD date, WORD time)
+{
+ int sec;
+ int min;
+ int hour;
+ int day;
+ int month;
+ int year;
+ char *pszAMPM; // AM/PM string
+
+ sec = (time & 0x1f) << 1;
+ min = (time >> 5) & 0x3f;
+ hour = (time >> 11) & 0x1f;
+
+ //** Get am/pm extension, and map 0 to 12
+ if (hour >= 12) {
+ pszAMPM = pszDD_TIME_PM;
+ hour -= 12;
+ }
+ else {
+ pszAMPM = pszDD_TIME_AM;
+ }
+ if (hour == 0) {
+ hour = 12;
+ }
+
+ day = (date & 0x1f);
+ month = (date >> 5) & 0x0f;
+ year = ((date >> 9) & 0x7f) + 1980;
+
+ MsgSet(psz,pszDD_DATE_TIME, "%02d%02d%02d%2d%02d%02d%s",
+ month, day, year, hour, min, sec, pszAMPM);
+} /* pszFromMSDOSTime() */