591 lines
16 KiB
C
591 lines
16 KiB
C
|
/*
|
||
|
** main.c - Main module for DOS command-line LZA file compression / expansion
|
||
|
** programs.
|
||
|
**
|
||
|
** Author: DavidDi
|
||
|
**
|
||
|
** This module is compiled twice - once for COMPRESS (COMPRESS defined) and
|
||
|
** once for EXPAND (COMPRESS not defined).
|
||
|
*/
|
||
|
|
||
|
|
||
|
// Headers
|
||
|
///////////
|
||
|
|
||
|
#include <malloc.h>
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <io.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <share.h>
|
||
|
#include <sys\types.h>
|
||
|
#include <sys\stat.h>
|
||
|
|
||
|
#include "lz_common.h"
|
||
|
#include "lz_buffers.h"
|
||
|
#include "lz_header.h"
|
||
|
|
||
|
#include "args.h"
|
||
|
#include "main.h"
|
||
|
#include "messages.h"
|
||
|
|
||
|
#include <diamondc.h>
|
||
|
#include "mydiam.h"
|
||
|
|
||
|
// Globals
|
||
|
///////////
|
||
|
|
||
|
CHAR ARG_PTR *pszInFileName, // input file name
|
||
|
*pszOutFileName, // output file name
|
||
|
*pszTargetName; // target path name
|
||
|
|
||
|
TCHAR ErrorMsg[1024];
|
||
|
|
||
|
|
||
|
// Module Variables
|
||
|
////////////////////
|
||
|
|
||
|
#ifndef COMPRESS
|
||
|
static BOOL bCopyingFile; // Is current file being copied or expanded?
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// Local Prototypes
|
||
|
////////////////////
|
||
|
|
||
|
static VOID DisplayErrorMessage(INT fError);
|
||
|
static VOID MakeDestFileName(CHAR ARG_PTR *argv[], CHAR ARG_PTR *pszDest);
|
||
|
static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf);
|
||
|
static BOOL ActuallyTheSameFile(CHAR ARG_PTR *pszFile1,
|
||
|
CHAR ARG_PTR *pszFile2);
|
||
|
static BOOL ProcessNotification(CHAR ARG_PTR *pszSource,
|
||
|
CHAR ARG_PTR *pszDest, WORD wNotification);
|
||
|
|
||
|
|
||
|
/*
|
||
|
** static void DisplayErrorMessage(int fError);
|
||
|
**
|
||
|
** Display error message for given error condition.
|
||
|
**
|
||
|
** Arguments: LZERROR_ code
|
||
|
**
|
||
|
** Returns: void
|
||
|
**
|
||
|
** Globals: none
|
||
|
*/
|
||
|
static VOID DisplayErrorMessage(INT fError)
|
||
|
{
|
||
|
switch(fError)
|
||
|
{
|
||
|
case LZERROR_BADINHANDLE:
|
||
|
LoadString(NULL, SID_NO_OPEN_INPUT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszInFileName);
|
||
|
break;
|
||
|
|
||
|
case LZERROR_BADOUTHANDLE:
|
||
|
LoadString(NULL, SID_NO_OPEN_OUTPUT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszOutFileName);
|
||
|
break;
|
||
|
|
||
|
case LZERROR_READ:
|
||
|
LoadString(NULL, SID_NO_READ_INPUT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszInFileName);
|
||
|
break;
|
||
|
|
||
|
case LZERROR_WRITE:
|
||
|
LoadString(NULL, SID_OUT_OF_SPACE, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszOutFileName);
|
||
|
break;
|
||
|
|
||
|
case BLANK_ERROR:
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
LoadString(NULL, SID_GEN_FAILURE, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszInFileName, pszOutFileName);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
** static void MakeDestFileName(char ARG_PTR *argv[], char ARG_PTR *pszDest);
|
||
|
**
|
||
|
** Create the appropriate destination file name.
|
||
|
**
|
||
|
** Arguments: argv - like argument to main()
|
||
|
** pszDest - pointer to destination file name buffer to be filled
|
||
|
** in
|
||
|
**
|
||
|
** Returns: void
|
||
|
**
|
||
|
** Globals: none
|
||
|
*/
|
||
|
static VOID MakeDestFileName(CHAR ARG_PTR *argv[], CHAR ARG_PTR *pszDest)
|
||
|
{
|
||
|
CHAR ARG_PTR *pszDestFile;
|
||
|
|
||
|
if (nNumFileSpecs == 2 && bTargetIsDir == FALSE && bDoRename == FALSE)
|
||
|
// Compress a single input file to a single output file. N.b., we must
|
||
|
// be careful to eat up the output file name command-line argument so
|
||
|
// it doesn't get processed like another input file!
|
||
|
STRCPY(pszDest, argv[GetNextFileArg(argv)]);
|
||
|
else if (bTargetIsDir == TRUE)
|
||
|
{
|
||
|
// Prepend output file name with destination directory path name.
|
||
|
STRCPY(pszDest, pszTargetName);
|
||
|
|
||
|
// Isolate source file name from source file specification.
|
||
|
pszDestFile = ExtractFileName(pszInFileName);
|
||
|
|
||
|
// Add destination file name to destination directory path
|
||
|
// specification.
|
||
|
MakePathName(pszDest, pszDestFile);
|
||
|
}
|
||
|
else
|
||
|
// Destination file name same as source file name. N.b., this is an
|
||
|
// error condition if (bDoRename == FALSE).
|
||
|
STRCPY(pszDest, pszInFileName);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
** static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf);
|
||
|
**
|
||
|
** Gets the canonical name for a given file specification.
|
||
|
**
|
||
|
** Arguments: pszFileName - file specification
|
||
|
** szCanonicalBuf - buffer to be filled with canonical name
|
||
|
**
|
||
|
** Returns: TRUE if successful. FALSE if unsuccessful.
|
||
|
**
|
||
|
** N.b., szCanonicalBuf must be at least 128 bytes long. The contents of
|
||
|
** szCanonicalBuf are only defined if the funstion returns TRUE.
|
||
|
**
|
||
|
*/
|
||
|
static BOOL GetCanonicalName(LPSTR lpszFileName, LPSTR lpszCanonicalBuf)
|
||
|
{
|
||
|
BOOL bRetVal = FALSE;
|
||
|
LPSTR lpszLastComp;
|
||
|
|
||
|
return((BOOL) GetFullPathName(lpszFileName, MAX_PATH, lpszCanonicalBuf, &lpszLastComp));
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
** static BOOL ActuallyTheSameFile(char ARG_PTR *pszFile1,
|
||
|
** char ARG_PTR *pszFile2);
|
||
|
**
|
||
|
** Checks to see if two file specifications point to the same physical file.
|
||
|
**
|
||
|
** Arguments: pszFile1 - first file specification
|
||
|
** pszFile2 - second file specification
|
||
|
**
|
||
|
** Returns: BOOL - TRUE if the file specifications point to the same
|
||
|
** physical file. FALSE if not.
|
||
|
**
|
||
|
** Globals: none
|
||
|
*/
|
||
|
static BOOL ActuallyTheSameFile(CHAR ARG_PTR *pszFile1,
|
||
|
CHAR ARG_PTR *pszFile2)
|
||
|
{
|
||
|
CHAR szCanonicalName1[MAX_PATH],
|
||
|
szCanonicalName2[MAX_PATH];
|
||
|
|
||
|
if (GetCanonicalName(pszFile1, szCanonicalName1) &&
|
||
|
GetCanonicalName(pszFile2, szCanonicalName2))
|
||
|
{
|
||
|
if (! lstrcmpiA(szCanonicalName1, szCanonicalName2))
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
** static BOOL ProcessNotification(char ARG_PTR *pszSource,
|
||
|
** char ARG_PTR *pszDest,
|
||
|
** WORD wNotification);
|
||
|
**
|
||
|
** Callback function during file processing.
|
||
|
**
|
||
|
** Arguments: pszSource - source file name
|
||
|
** pszDest - destination file name
|
||
|
** wNotification - process type query
|
||
|
**
|
||
|
** Returns: BOOL - (wNotification == NOTIFY_START_*):
|
||
|
** TRUE if the source file should be "processed" into
|
||
|
** the destination file. FALSE if not.
|
||
|
** else
|
||
|
** TRUE.
|
||
|
**
|
||
|
** Globals: none
|
||
|
*/
|
||
|
static BOOL ProcessNotification(CHAR ARG_PTR *pszSource,
|
||
|
CHAR ARG_PTR *pszDest, WORD wNotification)
|
||
|
{
|
||
|
switch(wNotification)
|
||
|
{
|
||
|
case NOTIFY_START_COMPRESS:
|
||
|
{
|
||
|
// Fail if the source and destination files are identical.
|
||
|
if (ActuallyTheSameFile(pszSource, pszDest))
|
||
|
{
|
||
|
LoadString(NULL, SID_COLLISION, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszSource);
|
||
|
return(FALSE);
|
||
|
}
|
||
|
|
||
|
// Display start message.
|
||
|
switch (byteAlgorithm)
|
||
|
{
|
||
|
case LZX_ALG:
|
||
|
LoadString(
|
||
|
NULL,
|
||
|
SID_COMPRESSING_LZX,
|
||
|
ErrorMsg,
|
||
|
1024
|
||
|
);
|
||
|
printf(ErrorMsg, pszSource, pszDest,
|
||
|
CompressionMemoryFromTCOMP(DiamondCompressionType)
|
||
|
);
|
||
|
break;
|
||
|
|
||
|
case QUANTUM_ALG:
|
||
|
LoadString(
|
||
|
NULL,
|
||
|
SID_COMPRESSING_QUANTUM,
|
||
|
ErrorMsg,
|
||
|
1024
|
||
|
);
|
||
|
printf(ErrorMsg, pszSource, pszDest,
|
||
|
CompressionLevelFromTCOMP(DiamondCompressionType),
|
||
|
CompressionMemoryFromTCOMP(DiamondCompressionType)
|
||
|
);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
LoadString(
|
||
|
NULL,
|
||
|
(byteAlgorithm == MSZIP_ALG) ? SID_COMPRESSING_MSZIP : SID_COMPRESSING,
|
||
|
ErrorMsg,
|
||
|
1024
|
||
|
);
|
||
|
printf(ErrorMsg, pszSource, pszDest);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return(TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// static BOOL FileTimeIsNewer( const char* pszFile1, const char* pszFile2 );
|
||
|
//
|
||
|
// Return value is TRUE if time stamp on pszFile1 is newer than the
|
||
|
// time stamp on pszFile2. If either of the two files do not exist,
|
||
|
// the return value is also TRUE (for indicating that pszFile2 should
|
||
|
// be update from pszFile1). Otherwise, the return value is FALSE.
|
||
|
//
|
||
|
|
||
|
static BOOL FileTimeIsNewer( const char* pszFile1, const char* pszFile2 ) {
|
||
|
|
||
|
struct _stat StatBufSource,
|
||
|
StatBufDest;
|
||
|
|
||
|
if (( _stat( pszFile2, &StatBufDest )) ||
|
||
|
( _stat( pszFile1, &StatBufSource )) ||
|
||
|
( StatBufSource.st_mtime > StatBufDest.st_mtime ))
|
||
|
return TRUE;
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
LPSTR
|
||
|
ValidListEntry(
|
||
|
LPSTR szArg
|
||
|
)
|
||
|
{
|
||
|
// Check for special character at front of file
|
||
|
if ( '@' == szArg[0] )
|
||
|
return szArg + 1;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
BOOL
|
||
|
GetNextFileListFile(
|
||
|
const LPSTR szFileList,
|
||
|
char **pszSource,
|
||
|
char **pszDest
|
||
|
)
|
||
|
{
|
||
|
static char szList[MAX_PATH] = {0},
|
||
|
szSource[MAX_PATH] = {0},
|
||
|
szDest[MAX_PATH] = {0};
|
||
|
static BOOL bParsingFile = FALSE;
|
||
|
static FILE *hFile;
|
||
|
static int dEntryNum = 1;
|
||
|
int dRetVal;
|
||
|
|
||
|
// Initialize out paramters to NULL
|
||
|
*pszSource = *pszDest = NULL;
|
||
|
|
||
|
// Open file if we are not currently parsing another one
|
||
|
if ( !bParsingFile ) {
|
||
|
// Do not reopen last file used as this is our signal to stop
|
||
|
if ( !_stricmp( szFileList, szList ) ) {
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// Attempt to open specified file
|
||
|
hFile = fopen( szFileList, "rt" );
|
||
|
if ( NULL == hFile ) {
|
||
|
LoadString( NULL, SID_NO_OPEN_INPUT, ErrorMsg, 1024 );
|
||
|
printf( ErrorMsg, szFileList );
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Store new file name in static buffer
|
||
|
strcpy( szList, szFileList );
|
||
|
|
||
|
bParsingFile = TRUE;
|
||
|
}
|
||
|
|
||
|
dRetVal = fscanf( hFile, "%s %s", szSource, szDest );
|
||
|
if ( EOF == dRetVal ) {
|
||
|
fclose( hFile );
|
||
|
bParsingFile = FALSE;
|
||
|
return TRUE;
|
||
|
}
|
||
|
else if ( 0 == dRetVal ) {
|
||
|
LoadString( NULL, SID_INVALID_LIST_FILE, ErrorMsg, 1024 );
|
||
|
printf( ErrorMsg, dEntryNum );
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
// Point to new source and destination entries
|
||
|
*pszSource = szSource;
|
||
|
*pszDest = szDest;
|
||
|
// Track entry
|
||
|
dEntryNum++;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
/*
|
||
|
** int main(int argc, char *argv[]);
|
||
|
**
|
||
|
** Run command-line file compression program.
|
||
|
**
|
||
|
** Arguments: figure it out
|
||
|
**
|
||
|
** Returns: int - EXIT_SUCCESS if compression finished successfully,
|
||
|
** EXIT_FAILURE if not.
|
||
|
**
|
||
|
** Globals: none
|
||
|
*/
|
||
|
INT __cdecl main(INT argc, CHAR *argv[])
|
||
|
{
|
||
|
INT iSourceFileName,
|
||
|
fError,
|
||
|
nTotalFiles = 0,
|
||
|
nReturnCode = EXIT_SUCCESS;
|
||
|
CHAR ARG_PTR pszDestFileName[MAX_PATH];
|
||
|
CHAR chTargetFileName[ MAX_PATH ];
|
||
|
LONG cblTotInSize = 0L,
|
||
|
cblTotOutSize = 0L;
|
||
|
|
||
|
PLZINFO pLZI;
|
||
|
|
||
|
USHORT wLanguageId = LANGIDFROMLCID(GetThreadLocale());
|
||
|
|
||
|
if ((LANG_JAPANESE == PRIMARYLANGID(wLanguageId)) ||
|
||
|
(LANG_KOREAN == PRIMARYLANGID(wLanguageId)) ||
|
||
|
(LANG_CHINESE == PRIMARYLANGID(wLanguageId)))
|
||
|
{
|
||
|
//
|
||
|
// This used to be #ifdef DBCS. Now a runtime check.
|
||
|
//
|
||
|
DWORD dw = GetConsoleOutputCP();
|
||
|
|
||
|
switch (dw) {
|
||
|
case 932:
|
||
|
case 936:
|
||
|
case 949:
|
||
|
case 950:
|
||
|
SetThreadLocale(MAKELCID(
|
||
|
MAKELANGID(
|
||
|
PRIMARYLANGID(GetSystemDefaultLangID()),
|
||
|
SUBLANG_ENGLISH_US),
|
||
|
SORT_DEFAULT));
|
||
|
break;
|
||
|
default:
|
||
|
SetThreadLocale(MAKELCID(
|
||
|
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||
|
SORT_DEFAULT ) );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parse command-line arguments.
|
||
|
if (ParseArguments(argc, argv) != TRUE)
|
||
|
return(EXIT_FAILURE);
|
||
|
|
||
|
// Display sign-on banner.
|
||
|
if ( bNoLogo == FALSE ) {
|
||
|
LoadString(NULL, SID_BANNER_TEXT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg);
|
||
|
}
|
||
|
|
||
|
// Set up global target path name.
|
||
|
pszTargetName = argv[iTarget];
|
||
|
|
||
|
if (bDisplayHelp == TRUE)
|
||
|
{
|
||
|
// User asked for help.
|
||
|
LoadString(NULL, SID_INSTRUCTIONS, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg);
|
||
|
LoadString(NULL, SID_INSTRUCTIONS2, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg);
|
||
|
LoadString(NULL, SID_INSTRUCTIONS3, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg);
|
||
|
return(EXIT_SUCCESS);
|
||
|
}
|
||
|
|
||
|
// Check for command line problems.
|
||
|
if (CheckArguments() == FALSE)
|
||
|
return(EXIT_FAILURE);
|
||
|
|
||
|
// Set up ring buffer and I/O buffers.
|
||
|
pLZI = InitGlobalBuffersEx();
|
||
|
if (!pLZI)
|
||
|
{
|
||
|
LoadString(NULL, SID_INSUFF_MEM, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg);
|
||
|
return(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
// Process each source file.
|
||
|
while ((iSourceFileName = GetNextFileArg(argv)) != FAIL)
|
||
|
{
|
||
|
char *pszFileList = NULL,
|
||
|
*pszCurFile,
|
||
|
*pszCurDestFile;
|
||
|
|
||
|
// Determine if this is a directive file
|
||
|
if ( pszFileList = ValidListEntry( argv[iSourceFileName] ) ) {
|
||
|
if ( !GetNextFileListFile( pszFileList, &pszCurFile, &pszCurDestFile ) ) {
|
||
|
return (EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
// Handle empty directive lists
|
||
|
if ( NULL == pszCurFile ) continue;
|
||
|
}
|
||
|
// Otherwise use current argument as file to compress
|
||
|
else {
|
||
|
pszCurFile = argv[iSourceFileName];
|
||
|
}
|
||
|
|
||
|
do {
|
||
|
// Set up global input file name.
|
||
|
pszInFileName = CharLowerA(pszCurFile);
|
||
|
|
||
|
// Set up global output file name.
|
||
|
if ( NULL == pszFileList ) {
|
||
|
MakeDestFileName(argv, pszDestFileName);
|
||
|
pszOutFileName = CharLowerA(pszDestFileName);
|
||
|
}
|
||
|
else {
|
||
|
pszOutFileName = CharLowerA(pszCurDestFile);
|
||
|
}
|
||
|
|
||
|
strcpy( chTargetFileName, pszOutFileName );
|
||
|
|
||
|
if ( bDoRename )
|
||
|
MakeCompressedName( chTargetFileName );
|
||
|
|
||
|
if (( ! bUpdateOnly ) ||
|
||
|
( FileTimeIsNewer( pszInFileName, chTargetFileName ))) {
|
||
|
|
||
|
if(DiamondCompressionType) {
|
||
|
fError = DiamondCompressFile(ProcessNotification,pszInFileName,
|
||
|
pszOutFileName,bDoRename,pLZI);
|
||
|
} else {
|
||
|
fError = Compress(ProcessNotification, pszInFileName,
|
||
|
pszOutFileName, byteAlgorithm, bDoRename, pLZI);
|
||
|
}
|
||
|
|
||
|
if(fError != TRUE)
|
||
|
// Deal with returned error codes.
|
||
|
DisplayErrorMessage(nReturnCode = fError);
|
||
|
else
|
||
|
{
|
||
|
nTotalFiles++;
|
||
|
|
||
|
if (pLZI && pLZI->cblInSize && pLZI->cblOutSize) {
|
||
|
|
||
|
// Keep track of cumulative statistics.
|
||
|
cblTotInSize += pLZI->cblInSize;
|
||
|
cblTotOutSize += pLZI->cblOutSize;
|
||
|
|
||
|
// Display report for each file.
|
||
|
LoadString(NULL, SID_FILE_REPORT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszInFileName, pLZI->cblInSize, pLZI->cblOutSize,
|
||
|
(INT)(100 - ((100 * (LONGLONG) pLZI->cblOutSize) / pLZI->cblInSize)));
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
LoadString(NULL, SID_EMPTY_FILE_REPORT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, pszInFileName, 0, 0);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// Separate individual file processing message blocks by a blank line.
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
// If we are processing a directive file, get the next arguments
|
||
|
if ( NULL != pszFileList ) {
|
||
|
if ( !GetNextFileListFile( pszFileList, &pszCurFile, &pszCurDestFile ) ) {
|
||
|
return (EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} while ( NULL != pszFileList && NULL != pszCurFile );
|
||
|
}
|
||
|
|
||
|
// Free memory used by ring buffer and I/O buffers.
|
||
|
FreeGlobalBuffers(pLZI);
|
||
|
|
||
|
// Display cumulative report for multiple files.
|
||
|
if (nTotalFiles > 1) {
|
||
|
|
||
|
// Scale results to get accurate %
|
||
|
LONG cblAdjInSize = cblTotInSize,
|
||
|
cblAdjOutSize = cblTotOutSize;
|
||
|
while (cblAdjInSize > 100000) {
|
||
|
cblAdjInSize /= 2;
|
||
|
cblAdjOutSize /= 2;
|
||
|
}
|
||
|
cblAdjOutSize += (cblAdjInSize / 200); // round off (+0.5%)
|
||
|
if (cblAdjOutSize < 0) {
|
||
|
cblAdjOutSize = 0;
|
||
|
}
|
||
|
|
||
|
LoadString(NULL, SID_TOTAL_REPORT, ErrorMsg, 1024);
|
||
|
printf(ErrorMsg, nTotalFiles, cblTotInSize, cblTotOutSize,
|
||
|
(INT)(100 - 100 * cblAdjOutSize / cblAdjInSize));
|
||
|
}
|
||
|
|
||
|
return(nReturnCode);
|
||
|
}
|
||
|
|