/* see README.TXT for license information. You are bound by this license. */
////////////////////////////////////////////////////////////////
// wrt_vx_imgtool
// wrt54gv5_imgbuild.cpp
// Author: Jeremy Collake (jeremy@bitsum.com)
// Site: http://www.bitsum.com (see Wiki, WRT54Gv5 linux flashing)
#define VERSION_TEXT "v0.94 beta"
//
// Extracts, views, and builds WRT54G/GS v5 and WRT54G/GS v6 firmware format.
//
// Revisions:
//
// 0.94 beta - fixed bug where -v and -x require -c (oops). Thanks wa6725.
// 0.92 beta - added linux g++ compatibility and linux Makefile
// 0.91 beta - cosmetic tweaks
// 0.90 beta - Renamed to wrt_vx_imgtool
// Added support for WRT54GSv5 and unknown models.
// Added -d switch to provide device name.
// Added -m switch to over-ride vendor name.
// Added -c switch to over-ride code pattern.
// Added sanity check for non-32-bit builds.
// 0.06 alpha - Polished up a bit.
// 0.05 alpha - Subtracted BOOTP size from BOOTROM image for BOOTP area,
// so it's now padded to 319kb.
// 0.04 alpha - Added 'trailer' file types and sizes as a result
// identification of these previously unknown members
// of VxLinksysHeader.
// 0.03 alpha - Added -f (fix checksum of existing firmware)
// Added support for bootrom.bin (1). Auto pads if incorrect size.
// Fixed problem with -v incorrectly calculating checksums on images with last DWORD not NULL.
// 0.02 alpha - Added build capability.
// 0.01 alpha - Early alpha. No firmware building supoprt yet.
//
//
//
//
#include <stdio.h>
#include <time.h>
#include <string>
#include <vector>
#include "imghdr.h"
// for linux you should define this on g++ cmd line
//#define _LINUX
#ifdef _LINUX
#define _strcmpi strcasecmp
#define strcmpi strcasecmp
// oh come on...no strlwr in ISO standard? here's my own
char *strlwr(char *str)
{
if(!str) return str;
for(int nI=0;str[nI];nI++)
{
str[nI]=tolower(str[nI]);
}
return str;
}
#endif
using namespace std;
// parallel arrays giving known devices
// and their code patterns, model names, and vendors
char *g_pszDeviceNames[]=
{
"WRT54Gv5",
"WRT54Gv6",
"WRT54GSv5",
NULL
};
char *g_pszModelNames[]=
{
"WRT54G",
"WRT54G",
"WRT54GS",
NULL
};
char *g_pszVendorNames[]=
{
"LINKSYS",
"LINKSYS",
"LINKSYS",
NULL
};
char *g_pszCodePatterns[]=
{
"WGV5",
"WGV5",
"WGS5",
NULL
};
// always flip, regardless of endianness of machine
unsigned long flip_endian(unsigned long nValue)
{
// my crappy endian switch
unsigned long nR;
unsigned long nByte1=(nValue&0xff000000)>>24;
unsigned long nByte2=(nValue&0x00ff0000)>>16;
unsigned long nByte3=(nValue&0x0000ff00)>>8;
unsigned long nByte4=nValue&0x0ff;
nR=nByte4<<24;
nR|=(nByte3<<16);
nR|=(nByte2<<8);
nR|=nByte1;
return nR;
}
unsigned long big_endian_l(unsigned long nValue)
{
#ifdef BIG_ENDIAN
return nValue;
#else
// my crappy endian switch
unsigned long nR;
unsigned long nByte1=(nValue&0xff000000)>>24;
unsigned long nByte2=(nValue&0x00ff0000)>>16;
unsigned long nByte3=(nValue&0x0000ff00)>>8;
unsigned long nByte4=nValue&0x0ff;
nR=nByte4<<24;
nR|=(nByte3<<16);
nR|=(nByte2<<8);
nR|=nByte1;
return nR;
#endif
}
unsigned short big_endian_s(unsigned short nValue)
{
#ifdef BIG_ENDIAN
return nValue;
#else
// my crappy endian switch
unsigned long nR;
unsigned long nByte1=(nValue&0xff00)>>8;
unsigned long nByte2=nValue&0x0ff;
nR=nByte1;
nR|=(nByte2<<8);
return (unsigned short)nR;
#endif
}
/////////////////////////////////////////////////////////////
// Checksum_Linksys_WRT54Gv5_v6
//
// unsigned 32bit checksum of 32bit unsigned integer - endian neutral
//
unsigned long
Checksum_Linksys_WRT54Gv5_v6(unsigned long *pStart, unsigned long *pEnd)
{
unsigned long nChecksum=0;
while(pStart<pEnd)
{
nChecksum+=big_endian_l(*pStart++);
}
return ~(nChecksum-1); // return two's compliment
}
bool SanityChecks()
{
#ifndef BIG_ENDIAN
if(big_endian_l(0x11223344)==0x11223344)
{
printf("\n ! ERROR: Endianness not set correctly. Define BIG_ENDIAN.");
return false;
}
#endif
if(sizeof(VxLinksysHeader)!=0x200)
{
printf("\n ! ERROR: Header definition is incorrect .. %d (%X)",sizeof(VxLinksysHeader),sizeof(VxLinksysHeader));
return false;
}
if(sizeof(DWORD)!=4)
{
printf("\n ! Please update data type defintions in imghdr.h for this system.");
return false;
}
else
{
//printf("\n + Sanity check: Header size is 0x%X",sizeof(VxLinksysHeader));
}
return true;
}
int VxFileIdToName(int nId, char *pszFilename, int nMaxLen)
{
//
// todo: switch to strcpy_n.. see if g++ has 'em
// todo: add other lang packs.. if anyone cares
//
switch(nId)
{
case VX_FILE_ID_BOOTROMBIN:
strcpy(pszFilename,"bootrom.bin");
return 1;
case VX_FILE_ID_VXWORKSBIN:
strcpy(pszFilename,"vxworks.bin");
return 1;
case VX_FILE_ID_IGWHTMDAT:
strcpy(pszFilename,"igwhtm.dat");
return 1;
case VX_FILE_ID_LANGPAK_EN:
strcpy(pszFilename,"langpak_en.dat");
return 1;
}
return 0;
}
int VxFileNameToId(const char *pszFilename)
{
if(!pszFilename || !strlen(pszFilename)) return -1;
char *pszFile=new char[strlen(pszFilename)+1];
strcpy(pszFile,pszFilename);
strlwr(pszFile);
int nR=0;
if(strstr(pszFile,"bootrom.bin"))
{
nR=VX_FILE_ID_BOOTROMBIN;
}
else if(strstr(pszFile,"vxworks.bin"))
{
nR=VX_FILE_ID_VXWORKSBIN;
}
else if(strstr(pszFile,"igwhtm.dat"))
{
nR=VX_FILE_ID_IGWHTMDAT;
}
else if(strstr(pszFile,"langpak_en.dat"))
{
nR=VX_FILE_ID_LANGPAK_EN;
}
delete pszFile;
return nR;
}
long EmitFile(unsigned char *pFirmwareImage, unsigned long nOffset, unsigned long nSize, const char *pszFilename)
{
printf("\n Writing file %s", pszFilename);
FILE *fOut=fopen(pszFilename,"wb");
if(!fOut)
{
printf("\n ! ERROR: Opening %s", pszFilename);
return -1;
}
unsigned char *p1=pFirmwareImage+nOffset;
if(fwrite(p1,1,nSize,fOut)!=nSize)
{
printf("\n ! ERROR: Writing %s", pszFilename);
fclose(fOut);
return -1;
}
fclose(fOut);
return nSize;
}
void store_bigendian_l(unsigned long *pDest, unsigned long nValue)
{
*pDest=big_endian_l(nValue);
}
void store_bigendian_s(unsigned short *pDest, unsigned short nValue)
{
*pDest=big_endian_s(nValue);
}
///////////////////////////////////////////
// BuildImage
//
// return: number of files extracted, or <= 0 for error.
//
int BuildImage(vector<string> &vInputFiles, const char *pszOutFile, char *pszCodePattern, char *pszModelName, char *pszVendorName)
{
printf("\n\n Building firmware image %s", pszOutFile);
FILE *fOut=fopen(pszOutFile,"wb");
if(!fOut)
{
printf("\n ! ERROR: Opening %s for writing", pszOutFile);
return -1;
}
// first, calculate total size
unsigned long nFirmwareSize=sizeof(VxLinksysHeader);
for(unsigned int nI=0;nI<vInputFiles.size();nI++)
{
FILE *fTest=fopen(vInputFiles[nI].c_str(),"rb");
if(!fTest)
{
printf("\n ! ERROR: Opening %s", vInputFiles[nI].c_str());
fclose(fOut);
return -1;
}
fseek(fTest,0,SEEK_END);
unsigned long nSize=
ftell(fTest);
printf("\n + Size of %s is %d bytes.", vInputFiles[nI].c_str(), nSize);
//todo: warn if size >
if(VxFileNameToId(vInputFiles[nI].c_str())==VX_FILE_ID_BOOTROMBIN && nSize<BOOTROM_SIZE)
{
unsigned long nDifference=BOOTROM_SIZE-nSize;
printf("\n ! WARNING: BOOTROM.BIN must be %d bytes. Padding end to make correct size.", BOOTROM_SIZE);
printf("\n Padding will be %d bytes in length.", nDifference);
nSize=BOOTROM_SIZE;
}
nFirmwareSize+=nSize;
fclose(fTest);
}
// align to 32-bit boundary
nFirmwareSize+=3;
nFirmwareSize/=4;
nFirmwareSize*=4;
// allocate buffer
unsigned char *buffer=new unsigned char[nFirmwareSize];
memset(buffer,0,nFirmwareSize);
pVxLinksysHeader pHeader=(pVxLinksysHeader)buffer;
printf("\n + Building header");
// fill header todo: don't use strcpy on longs..
// reverse code pattern
DWORD *lRevCodePattern=(DWORD *)pszCodePattern;
DWORD lNewCodePattern=flip_endian(*lRevCodePattern);
memcpy(&pHeader->nCodePattern,&lNewCodePattern,sizeof(DWORD));
strcpy((char *)&pHeader->cImageFormatVersion,"U2ND");
strcpy((char *)&pHeader->cVendorName[0],pszVendorName); //"LINKSYS");
strcpy((char *)&pHeader->cModelName[0],pszModelName); //"WRT54G");
// store local time
time_t t;
struct tm *pTime;
time(&t);
pTime = localtime(&t);
pHeader->cDay=pTime->tm_mday;
pHeader->cMonth=pTime->tm_mon+1;
pHeader->cYear=pTime->tm_year-100;
//todo: versions not defined right yet
pHeader->nProductVersion_0=0x05;
pHeader->nMinorVersion_0=0x90; // x.00.9 is 0x90 here.. x.00.6 is 0x60
pHeader->nProductVersion_1=0x05;
pHeader->nMajorVersion_1=0x01;
pHeader->nMajorVersion_1=0x90;
store_bigendian_s(&pHeader->nHeaderSizeBigEnd,0x200);
store_bigendian_s(&pHeader->nUnknown_10D,2);
// todo: skip these for now since they seem to have no effect
printf("\n + Setting trailer file sizes to 0.. (todo: add support?)");
// of course, they're already 0.. just saying that to remmeber about them
// now store file descriptors and read files
unsigned long nCurrentPos=sizeof(VxLinksysHeader);
for(unsigned int nI=0;nI<vInputFiles.size();nI++)
{
printf("\n + Storing %s", vInputFiles[nI].c_str());
FILE *fTest=fopen(vInputFiles[nI].c_str(),"rb");
if(!fTest)
{
printf("\n ! ERROR: Opening %s", vInputFiles[nI].c_str());
delete buffer;
fclose(fOut);
return -1;
}
fseek(fTest,0,SEEK_END);
unsigned long nSize=
ftell(fTest);
fseek(fTest,0,SEEK_SET);
unsigned char *tmp=new unsigned char[nSize];
if(fread(tmp,1,nSize,fTest)!=nSize)
{
printf("\n ! ERROR: Reading %s", vInputFiles[nI].c_str());
delete buffer;
delete tmp;
fclose(fOut);
return -1;
}
memcpy(buffer+nCurrentPos,tmp,nSize);
delete tmp;
nCurrentPos+=nSize;
// now store the descriptor
pVxFileDescriptor pFileDescriptor=&pHeader->FileDescriptors[nI];
store_bigendian_l(&pFileDescriptor->nFileId_BigEnd,VxFileNameToId(vInputFiles[nI].c_str()));
if(!pFileDescriptor->nFileId_BigEnd)
{
printf("\n ! WARNING: Unrecognized file id! Remember to name your OS ELF vxworks.bin (even if its vmlinux).");
}
else if(big_endian_l(pFileDescriptor->nFileId_BigEnd)==VX_FILE_ID_BOOTROMBIN)
{
if(nSize<BOOTROM_SIZE)
{
unsigned long nDifference=BOOTROM_SIZE-nSize;
unsigned char *pPad=new unsigned char[nDifference];
printf("\n + Padding bootrom.bin with %d bytes", nDifference);
memset(pPad,0,nDifference);
memcpy(buffer+nCurrentPos,pPad,nDifference);
nSize=BOOTROM_SIZE;
nCurrentPos+=nDifference;
delete pPad;
}
}
fclose(fTest);
char szName[256];
VxFileIdToName(big_endian_l(pFileDescriptor->nFileId_BigEnd),szName,sizeof(szName));
printf("\n + Stored with file type %d (%s)", big_endian_l(pFileDescriptor->nFileId_BigEnd),szName);
store_bigendian_l(&pFileDescriptor->nFileSize_BigEnd,nSize);
}
if(nCurrentPos>nFirmwareSize)
{
printf("\n ERROR: Sanity check fails. current pos > firmware size");
delete buffer;
fclose(fOut);
return -1;
}
unsigned char *pEnd=buffer+nFirmwareSize;
store_bigendian_l(&pHeader->nChecksumBigEnd,Checksum_Linksys_WRT54Gv5_v6((unsigned long *)buffer,(unsigned long *)pEnd));
printf("\n + Checksum is %08X", big_endian_l(pHeader->nChecksumBigEnd));
if(fwrite(buffer,1,nFirmwareSize,fOut)!=nFirmwareSize)
{
printf("\n ERROR: Writing firmware image to disk.");
delete buffer;
fclose(fOut);
return -1;
}
printf("\n Firmware size is %d bytes.", nFirmwareSize);
delete buffer;
fclose(fOut);
return nFirmwareSize;
}
///////////////////////////////////////////
// ExtractImage
//
// return: number of files extracted, or <= 0 for error.
//
int ExtractImage(const char *pszInput, const char *pszOutFolder, bool bViewOnly)
{
printf("\n\n Extracting firmware %s", pszInput);
// make a local copy of the output folder for safe modification
char szOutputFolder[4096];
if(pszOutFolder && strlen(pszOutFolder))
{
strcpy(szOutputFolder,pszOutFolder);
if(szOutputFolder[strlen(pszOutFolder)-1]!='\\'
&&
szOutputFolder[strlen(pszOutFolder)-1]!='/')
{
//
// I've never coded for *nix before, do file i/o
// functions handle backslashes in pathnames like a forward
// slash? Here's a BAD check in case since we
// night not be using MSC under Windows...
//
#ifdef _MSC_VER
strcat(szOutputFolder,"\\");
#else
strcat(szOutputFolder,"/");
#endif
}
}
else
{
szOutputFolder[0]=0;
}
FILE *fIn=fopen(pszInput,"rb");
if(!fIn)
{
printf("\n ERROR: Opening file %s", pszInput);
return -1;
}
fseek(fIn,0,SEEK_END);
long nFilesize=ftell(fIn);
fseek(fIn,0,SEEK_SET);
if(nFilesize<sizeof(VxLinksysHeader))
{
printf("\n ERROR: File too small: %s", pszInput);
return -1;
}
printf("\n Firmare file size is %d bytes", nFilesize);
unsigned char *buffer=new unsigned char[nFilesize];
fread((void *)&buffer[0],1,nFilesize,fIn);
pVxLinksysHeader pHeader=(pVxLinksysHeader)buffer;
unsigned long nChecksum=big_endian_l(pHeader->nChecksumBigEnd);
pHeader->nChecksumBigEnd=0;
unsigned char *pEnd=buffer+nFilesize;
unsigned long nCalculatedChecksum=Checksum_Linksys_WRT54Gv5_v6(
(unsigned long *)buffer,
(unsigned long *)pEnd);
unsigned int cMonth=pHeader->cMonth;
unsigned int cDay=pHeader->cDay;
unsigned short cYear=pHeader->cYear;
/*
unsigned short nMajorVer0=pHeader->nProductVersion_0;
unsigned short nMinorVer0=pHeader->nMinorVersion_0;
unsigned short nProductVer1=big_endian_s(pHeader->nProductVersion_1);
unsigned short nMajorVer1=pHeader->nProductVersion_1;
unsigned short nMinorVer1=pHeader->nMinorVersion_1;
*/
printf("\n"
"\n Code pattern: %s"
//"\n Version: %d.%d [%d.%d.%x]"
"\n Build date: %02d-%02d-%02d"
"\n Vendor name: %s"
"\n Device name: %s"
"\n Checksum: 0x%08X (given)"
"\n Checksum: 0x%08X (calculated)"
"\n Checksum %s",
(char *)&pHeader->nCodePattern,
//nMajorVer0,
//nMinorVer0,
//nProductVer1,
//nMajorVer1,
//nMinorVer1,
cMonth,
cDay,
cYear,
(char *)&pHeader->cVendorName,
pHeader->cModelName,
nChecksum,
nCalculatedChecksum,
nChecksum==nCalculatedChecksum?"CORRECT":"INCORRECT");
printf("\n\n + Trailing files:");
int nFileCount=0;
unsigned long nFilesizeSum=sizeof(VxLinksysHeader);
for(int nI=0;nI<8;nI++)
{
pVxFileDescriptor pFileDescriptor=&pHeader->TrailingFiles[nI];
unsigned long nIntFilesize=big_endian_l(pFileDescriptor->nFileSize_BigEnd);
if(nIntFilesize)
{
printf(
"\n -"
"\n File descriptor %d"
"\n Type Id: %d"
"\n Name: unknown"
"\n Size: %d",
nI,
big_endian_l(pFileDescriptor->nFileId_BigEnd),
nIntFilesize);
if(!bViewOnly && nIntFilesize)
{
char szIntFilename[256];
sprintf(szIntFilename,"trailing_file_%d",nI);
string sFullpath=szOutputFolder;
sFullpath+=szIntFilename;
if(!EmitFile(buffer,nFilesizeSum,big_endian_l(pFileDescriptor->nFileSize_BigEnd),sFullpath.c_str()))
{
nFileCount=-1;
printf("\n ERROR: Extracting file from firmware (disk/filesystem problem).");
}
}
nFilesizeSum+=nIntFilesize;
if(nFileCount!=-1) nFileCount++;
}
}
printf("\n\n + Primary files:");
for(int nI=0;nI<8;nI++)
{
pVxFileDescriptor pFileDescriptor=&pHeader->FileDescriptors[nI];
char szIntFilename[256]={0};
VxFileIdToName(big_endian_l(pFileDescriptor->nFileId_BigEnd),szIntFilename,sizeof(szIntFilename));
unsigned long nIntFilesize=big_endian_l(pFileDescriptor->nFileSize_BigEnd);
printf(
"\n -"
"\n File descriptor %d"
"\n Type Id: %d"
"\n Name: %s"
"\n Size: %d",
nI,
big_endian_l(pFileDescriptor->nFileId_BigEnd),
szIntFilename,
nIntFilesize);
if(!bViewOnly && nIntFilesize)
{
if(!szIntFilename[0])
{
sprintf(szIntFilename,"__unused_file_%d",nI);
}
string sFullpath=szOutputFolder;
sFullpath+=szIntFilename;
if(!EmitFile(buffer,nFilesizeSum,big_endian_l(pFileDescriptor->nFileSize_BigEnd),sFullpath.c_str()))
{
nFileCount=-1;
printf("\n ERROR: Extracting file from firmware (disk/filesystem problem).");
}
}
nFilesizeSum+=nIntFilesize;
if(nFileCount!=-1) nFileCount++;
}
//todo: figure out why 4 off when trailing files presnt
/*
if(nFilesizeSum!=nFilesize)
{
printf("\n ! WARNING: Firmware file size does not add up! It is %d and supposed to be %d.",nFilesize,nFilesizeSum);
}
*/
delete buffer;
fclose(fIn);
return nFileCount;
}
int FixImage(const char *pszImage)
{
printf("\n\n Fixing checksum on %s", pszImage);
FILE *fIn=fopen(pszImage,"r+b");
if(!fIn)
{
printf("\n ! ERROR: Opening image.");
return -1;
}
fseek(fIn,0,SEEK_END);
unsigned long nFilesize=ftell(fIn);
fseek(fIn,0,SEEK_SET);
unsigned char *buffer=new unsigned char[nFilesize];
if(fread(buffer,1,nFilesize,fIn)!=nFilesize)
{
printf("\n ! ERROR: Reading image.");
delete buffer;
fclose(fIn);
return -1;
}
pVxLinksysHeader pHeader=(pVxLinksysHeader)buffer;
printf("\n Original checksum was 0x%08X", big_endian_l(pHeader->nChecksumBigEnd));
pHeader->nChecksumBigEnd=0;
unsigned char *pEnd=buffer+nFilesize;
unsigned long nChecksum=Checksum_Linksys_WRT54Gv5_v6((unsigned long *)buffer, (unsigned long *)pEnd);
store_bigendian_l(&pHeader->nChecksumBigEnd,nChecksum);
printf("\n New checksum is 0x%08X", big_endian_l(pHeader->nChecksumBigEnd));
fseek(fIn,0,SEEK_SET);
if(fwrite(buffer,1,nFilesize,fIn)!=nFilesize)
{
printf("\n ! ERROR: Writing image.");
delete buffer;
fclose(fIn);
return -1;
}
fclose(fIn);
return 1;
}
int main(int argc, char* argv[])
{
printf("\n WRT54G/GS v5-v6 firmware image builder, extractor, fixer, and viewer");
printf("\n "VERSION_TEXT " - " __DATE__ " by Jeremy Collake (jeremy@bitsum.com)");
//printf("\n For info see: http://www.bitsum.com/openwiking/owbase/ow.asp?WRT54G5%5FCFE");
printf("\n ------------------------------------------------------------------------------");
if(argc<2)
{
showhelp:
printf("\n\n Usage:"
"\n"
"\n wrt_vx_imgtool"
"\n [-x|v|f|b] [-d device] [-c abc] [-m abc] -o outfile infile1 infile2 ..."
"\n"
"\n Operations:"
"\n"
"\n [-b] Build the firmware (default)"
"\n [-x] Extract the firmware"
"\n The image filename should be provided as the first, and only, 'infile'"
"\n parameter. The -o switch can specify an output directory, if the CWD"
"\n isn't desired. All files, primary and trailing, are extacted to "
"\n the output folder, named in accordance with their type."
"\n [-v] Dump/analyze the firmware"
"\n Similar to extraction, but no files are writen to disk"
"\n [-f] Just fix the checksum of given input firmware"
"\n"
"\n Options:"
"\n"
"\n [-d] Set target device. Causes the code pattern and vendor name to be set to"
"\n proper values. By default the device is the WRT54G. Valid devices:"
"\n WRT54Gv5"
"\n WRT54Gv6"
"\n WRT54GSv5"
"\n [-c] Over-rides the code pattern. Not recommended."
"\n [-m] Over-rides the vendor name. Not recommended."
"\n"
"\n Notes:"
"\n"
"\n The deafult action is to build a new firmware, saved in outfile,"
"\n and containing files supplied as input. The files should be named"
"\n in accordance with their file type/flash area."
"\n"
"\n The following files are normally included in the firmware images:"
"\n vxWorks.bin"
"\n igwhtm.dat"
"\n langpak_en"
"\n __trailing__file_1"
"\n __trailing__file_2"
"\n ..."
"\n"
"\n These files will be created if extraction is chosen, or should be"
"\n supplied when building a firmware image."
"\n\n");
return 1;
}
bool bExtract=false;
bool bView=false;
bool bFix=false;
bool bPause=false;
char *pszDeviceName=g_pszDeviceNames[0];
char *pszVendorName=g_pszVendorNames[0];
char *pszModelName=g_pszModelNames[0];
char *pszCodePattern=NULL;
string sOutParam;
vector<string> vInFiles;
for(int nI=1;nI<argc;nI++)
{
if(argv[nI][0]=='-')
{
switch(argv[nI][1])
{
case '?':
case 'h':
case 'H':
goto showhelp;
case 'o':
case 'O':
if(nI+1>=argc)
{
printf("\n ERROR: Missing parameter second half.");
printf("\n");
return -1;
}
printf("\n + Found parameter, outfile %s", argv[++nI]);
sOutParam=argv[nI];
break;
case 'X':
case 'x':