600 lines
17 KiB
C
600 lines
17 KiB
C
/****************************** Module Header ******************************\
|
|
* Module Name: shlexts.c
|
|
*
|
|
* Copyright (c) 1997, Microsoft Corporation
|
|
*
|
|
* This module contains user related debugging extensions.
|
|
*
|
|
* History:
|
|
* 10/28/97 created by cdturner (butchered from the userexts.dll)
|
|
\***************************************************************************/
|
|
|
|
#include <precomp.h>
|
|
#pragma hdrstop
|
|
|
|
#include <winver.h>
|
|
#include <shlwapi.h>
|
|
|
|
|
|
char * pszExtName = "SHLEXTS";
|
|
|
|
#include <stdexts.h>
|
|
#include <stdexts.c>
|
|
|
|
BOOL bShowFlagNames = TRUE;
|
|
#define NO_FLAG INVALID_HANDLE_VALUE // use this for non-meaningful entries.
|
|
|
|
LPSTR apszSFGAOFlags[] =
|
|
{
|
|
"SFGAO_CANCOPY", // 0x00000001L
|
|
"SFGAO_CANMOVE", // 0x00000002L
|
|
"SFGAO_CANLINK", // 0x00000004L
|
|
NO_FLAG,
|
|
"SFGAO_CANRENAME", // 0x00000010L // Objects can be renamed
|
|
"SFGAO_CANDELETE", // 0x00000020L // Objects can be deleted
|
|
"SFGAO_HASPROPSHEET", // 0x00000040L // Objects have property sheets
|
|
NO_FLAG,
|
|
"SFGAO_DROPTARGET", // 0x00000100L // Objects are drop target
|
|
NO_FLAG,
|
|
NO_FLAG,
|
|
NO_FLAG,
|
|
"SFGAO_LINK", // 0x00010000L // Shortcut (link)
|
|
"SFGAO_SHARE", // 0x00020000L // shared
|
|
"SFGAO_READONLY", // 0x00040000L // read-only
|
|
"SFGAO_GHOSTED", // 0x00080000L // ghosted icon
|
|
"SFGAO_NONENUMERATED", // 0x00100000L // is a non-enumerated object
|
|
"SFGAO_NEWCONTENT", // 0x00200000L // should show bold in explorer tree
|
|
NO_FLAG,
|
|
NO_FLAG,
|
|
"SFGAO_VALIDATE", // 0x01000000L // invalidate cached information
|
|
"SFGAO_REMOVABLE", // 0x02000000L // is this removeable media?
|
|
"SFGAO_COMPRESSED", // 0x04000000L // Object is compressed (use alt color)
|
|
"SFGAO_BROWSABLE", // 0x08000000L // is in-place browsable
|
|
"SFGAO_FILESYSANCESTOR",// 0x10000000L // It contains file system folder
|
|
"SFGAO_FOLDER", // 0x20000000L // It's a folder.
|
|
"SFGAO_FILESYSTEM", // 0x40000000L // is a file system thing (file/folder/root)
|
|
"SFGAO_HASSUBFOLDER", // 0x80000000L // Expandable in the map pane
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszSLDFFlags[] =
|
|
{
|
|
"SLDF_HAS_ID_LIST", // = 0x0001, // Shell link saved with ID list
|
|
"SLDF_HAS_LINK_INFO", // = 0x0002, // Shell link saved with LinkInfo
|
|
"SLDF_HAS_NAME", // = 0x0004,
|
|
"SLDF_HAS_RELPATH", // = 0x0008,
|
|
"SLDF_HAS_WORKINGDIR", // = 0x0010,
|
|
"SLDF_HAS_ARGS", // = 0x0020,
|
|
"SLDF_HAS_ICONLOCATION", // = 0x0040,
|
|
"SLDF_UNICODE", // = 0x0080, // the strings are unicode (NT is comming!)
|
|
"SLDF_FORCE_NO_LINKINFO",// = 0x0100, // don't create a LINKINFO (make a dumb link)
|
|
"SLDF_HAS_EXP_SZ" // = 0x0200, // the link contains expandable env strings
|
|
"SLDF_RUN_IN_SEPARATE", // = 0x0400, // Run the 16-bit target exe in a separate VDM/WOW
|
|
"SLDF_HAS_LOGO3ID", // = 0x0800, // this link is a special Logo3/MSICD link
|
|
"SLDF_HAS_DARWINID", // = 0x1000 // this link is a special Darwin link
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszFWFFlags[] =
|
|
{
|
|
"FWF_AUTOARRANGE", // = 0x0001,
|
|
"FWF_ABBREVIATEDNAMES", // = 0x0002,
|
|
"FWF_SNAPTOGRID", // = 0x0004,
|
|
"FWF_OWNERDATA", // = 0x0008,
|
|
"FWF_BESTFITWINDOW", // = 0x0010,
|
|
"FWF_DESKTOP", // = 0x0020,
|
|
"FWF_SINGLESEL", // = 0x0040,
|
|
"FWF_NOSUBFOLDERS", // = 0x0080,
|
|
"FWF_TRANSPARENT", // = 0x0100,
|
|
"FWF_NOCLIENTEDGE", // = 0x0200,
|
|
"FWF_NOSCROLL", // = 0x0400,
|
|
"FWF_ALIGNLEFT", // = 0x0800,
|
|
"FWF_NOICONS", // = 0x1000,
|
|
"FWF_SINGLECLICKACTIVATE", // = 0x8000 // TEMPORARY -- NO UI FOR THIS
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszICIFlags[] =
|
|
{
|
|
"ICIFLAG_LARGE", // 0x0001
|
|
"ICIFLAG_SMALL", // 0x0002
|
|
"ICIFLAG_BITMAP", // 0x0004
|
|
"ICIFLAG_ICON", // 0x0008
|
|
"ICIFLAG_INDEX", // 0x0010
|
|
"ICIFLAG_NAME", // 0x0020
|
|
"ICIFLAG_FLAGS", // 0x0040
|
|
"ICIFLAG_NOUSAGE", // 0x0080
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszFDFlags[] =
|
|
{
|
|
"FD_CLSID", // = 0x0001,
|
|
"FD_SIZEPOINT", // = 0x0002,
|
|
"FD_ATTRIBUTES", // = 0x0004,
|
|
"FD_CREATETIME", // = 0x0008,
|
|
"FD_ACCESSTIME", // = 0x0010,
|
|
"FD_WRITESTIME", // = 0x0020,
|
|
"FD_FILESIZE", // = 0x0040,
|
|
"FD_LINKUI", // = 0x8000, // 'link' UI is prefered
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszSHCNEFlags[] =
|
|
{
|
|
"SHCNE_RENAMEITEM", // 0x00000001L
|
|
"SHCNE_CREATE", // 0x00000002L
|
|
"SHCNE_DELETE", // 0x00000004L
|
|
"SHCNE_MKDIR", // 0x00000008L
|
|
"SHCNE_RMDIR", // 0x00000010L
|
|
"SHCNE_MEDIAINSERTED", // 0x00000020L
|
|
"SHCNE_MEDIAREMOVED", // 0x00000040L
|
|
"SHCNE_DRIVEREMOVED", // 0x00000080L
|
|
"SHCNE_DRIVEADD", // 0x00000100L
|
|
"SHCNE_NETSHARE", // 0x00000200L
|
|
"SHCNE_NETUNSHARE", // 0x00000400L
|
|
"SHCNE_ATTRIBUTES", // 0x00000800L
|
|
"SHCNE_UPDATEDIR", // 0x00001000L
|
|
"SHCNE_UPDATEITEM", // 0x00002000L
|
|
"SHCNE_SERVERDISCONNECT", // 0x00004000L
|
|
"SHCNE_UPDATEIMAGE", // 0x00008000L
|
|
"SHCNE_DRIVEADDGUI", // 0x00010000L
|
|
"SHCNE_RENAMEFOLDER", // 0x00020000L
|
|
"SHCNE_FREESPACE", // 0x00040000L
|
|
NO_FLAG,
|
|
NO_FLAG,
|
|
NO_FLAG,
|
|
"SHCNE_EXTENDED_EVENT", // 0x04000000L
|
|
"SHCNE_ASSOCCHANGED", // 0x08000000L
|
|
NULL
|
|
};
|
|
|
|
LPSTR apszSSFFlags[] =
|
|
{
|
|
"SSF_SHOWALLOBJECTS", // 0x0001
|
|
"SSF_SHOWEXTENSIONS", // 0x0002
|
|
"SSF_WIN95UNUSED", // 0x0004 // ;Internal - corresponding SHELLSTATE fields don't exist in SHELLFLAGSTATE
|
|
"SSF_SHOWCOMPCOLOR", // 0x0008
|
|
"SSF_SORTCOLUMNS", // 0x0010 // ;Internal - corresponding SHELLSTATE fields don't exist in SHELLFLAGSTATE
|
|
"SSF_SHOWSYSFILES", // 0x0020
|
|
"SSF_DOUBLECLICKINWEBVIEW", // 0x0080
|
|
"SSF_SHOWATTRIBCOL", // 0x0100
|
|
"SSF_DESKTOPHTML", // 0x0200
|
|
"SSF_WIN95CLASSIC", // 0x0400
|
|
"SSF_DONTPRETTYPATH", // 0x0800
|
|
"SSF_MAPNETDRVBUTTON", // 0x1000
|
|
"SSF_SHOWINFOTIP", // 0x2000
|
|
"SSF_HIDEICONS", // 0x4000
|
|
"SSF_NOCONFIRMRECYCLE", // 0x8000
|
|
"SSF_FILTER", // 0x00010000 // ;Internal - corresponding SHELLSTATE fields don't exist in SHELLFLAGSTATE
|
|
"SSF_WEBVIEW", // 0x00020000 // ;Internal
|
|
"SSF_SHOWSUPERHIDDEN", // 0x00040000 // ;Internal
|
|
"SSF_SEPPROCESS", // 0x00080000 // ;Internal
|
|
"SSF_NONETCRAWLING", // 0x00100000 // ;Internal
|
|
"SSF_STARTPANELON", // 0x00200000 // ;Internal
|
|
|
|
NULL
|
|
};
|
|
|
|
enum GF_FLAGS {
|
|
GL_SFGAO = 0,
|
|
GL_SLDF,
|
|
GL_FWF,
|
|
GL_ICI,
|
|
GL_FD,
|
|
GL_SHCNE,
|
|
GL_SSF,
|
|
GF_MAX,
|
|
};
|
|
|
|
struct _tagFlags
|
|
{
|
|
LPSTR * apszFlags;
|
|
LPSTR pszFlagsname;
|
|
} argFlag[GF_MAX] =
|
|
{
|
|
{apszSFGAOFlags, "SFGAO"},
|
|
{apszSLDFFlags, "SLD"},
|
|
{apszFWFFlags, "FWF"},
|
|
{apszICIFlags, "ICIFLAG"},
|
|
{apszFDFlags, "FD"},
|
|
{apszSHCNEFlags, "SHCNE"},
|
|
{apszSSFFlags, "SSF"}
|
|
};
|
|
|
|
/************************************************************************\
|
|
* Procedure: GetFlags
|
|
*
|
|
* Description:
|
|
*
|
|
* Converts a 32bit set of flags into an appropriate string.
|
|
* pszBuf should be large enough to hold this string, no checks are done.
|
|
* pszBuf can be NULL, allowing use of a local static buffer but note that
|
|
* this is not reentrant.
|
|
* Output string has the form: "FLAG1 | FLAG2 ..." or "0"
|
|
*
|
|
* Returns: pointer to given or static buffer with string in it.
|
|
*
|
|
* 6/9/1995 Created SanfordS
|
|
* 11/5/1997 cdturner changed the aapszFlag type
|
|
*
|
|
\************************************************************************/
|
|
LPSTR GetFlags(
|
|
WORD wType,
|
|
DWORD dwFlags,
|
|
LPSTR pszBuf,
|
|
BOOL fPrintZero)
|
|
{
|
|
static char szT[512];
|
|
WORD i;
|
|
BOOL fFirst = TRUE;
|
|
BOOL fNoMoreNames = FALSE;
|
|
LPSTR *apszFlags;
|
|
|
|
if (pszBuf == NULL) {
|
|
pszBuf = szT;
|
|
}
|
|
if (!bShowFlagNames) {
|
|
sprintf(pszBuf, "%x", dwFlags);
|
|
return pszBuf;
|
|
}
|
|
|
|
*pszBuf = '\0';
|
|
|
|
if (wType >= GF_MAX) {
|
|
strcpy(pszBuf, "Invalid flag type.");
|
|
return pszBuf;
|
|
}
|
|
|
|
apszFlags = argFlag[wType].apszFlags;
|
|
|
|
for (i = 0; dwFlags; dwFlags >>= 1, i++) {
|
|
if (!fNoMoreNames && apszFlags[i] == NULL) {
|
|
fNoMoreNames = TRUE;
|
|
}
|
|
|
|
if (dwFlags & 1) {
|
|
if (!fFirst) {
|
|
strcat(pszBuf, " | ");
|
|
} else {
|
|
fFirst = FALSE;
|
|
}
|
|
|
|
if (fNoMoreNames || apszFlags[i] == NO_FLAG) {
|
|
char ach[16];
|
|
sprintf(ach, "0x%lx", 1 << i);
|
|
strcat(pszBuf, ach);
|
|
} else {
|
|
strcat(pszBuf, apszFlags[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fFirst && fPrintZero) {
|
|
sprintf(pszBuf, "0");
|
|
}
|
|
|
|
return pszBuf;
|
|
}
|
|
|
|
/************************************************************************\
|
|
* Procedure: Iflags
|
|
*
|
|
* Description:
|
|
*
|
|
* outputs the list of flags for the given flags type
|
|
*
|
|
* 11/5/1997 Created cdturner
|
|
*
|
|
\************************************************************************/
|
|
BOOL Iflags( DWORD dwOpts,
|
|
LPSTR pszArgs )
|
|
{
|
|
CHAR szBuffer[100];
|
|
int iOffset = 0;
|
|
int iFlags;
|
|
LPDWORD pAddr;
|
|
BOOL bAddr = FALSE;
|
|
DWORD dwValue;
|
|
LPSTR pszOut;
|
|
|
|
if ( dwOpts & OFLAG(l))
|
|
{
|
|
// list all the struct names
|
|
Print("Flags types known:\n");
|
|
|
|
for ( iFlags = 0; iFlags < GF_MAX; iFlags ++ )
|
|
{
|
|
sprintf( szBuffer, " %s\n", argFlag[iFlags].pszFlagsname);
|
|
Print( szBuffer );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// skip whitespace
|
|
while ( *pszArgs == ' ' )
|
|
pszArgs ++;
|
|
|
|
// now grab the flagsname
|
|
while ( pszArgs[iOffset] != ' ' && pszArgs[iOffset] != '\0' )
|
|
{
|
|
szBuffer[iOffset] = pszArgs[iOffset];
|
|
iOffset ++;
|
|
};
|
|
|
|
// terminate the string
|
|
szBuffer[iOffset] = 0;
|
|
|
|
// find the flags value
|
|
for ( iFlags = 0; iFlags < GF_MAX; iFlags ++ )
|
|
{
|
|
if ( lstrcmpA( szBuffer, argFlag[iFlags].pszFlagsname ) == 0 )
|
|
break;
|
|
}
|
|
|
|
if ( iFlags >= GF_MAX )
|
|
{
|
|
Print( "unknown flagsname - ");
|
|
Print( szBuffer );
|
|
Print( "\n" );
|
|
return TRUE;
|
|
}
|
|
|
|
// skip white space
|
|
while ( pszArgs[iOffset] == ' ' )
|
|
iOffset ++;
|
|
|
|
if ( pszArgs[iOffset] == '*' )
|
|
{
|
|
bAddr = TRUE;
|
|
iOffset ++;
|
|
}
|
|
|
|
pAddr = (LPDWORD) EvalExp( pszArgs + iOffset );
|
|
|
|
if ( bAddr )
|
|
{
|
|
if ( !tryDword( &dwValue, pAddr ) )
|
|
{
|
|
Print( "unable to access memory at that location\n");
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwValue = PtrToUlong(pAddr);
|
|
}
|
|
|
|
pszOut = GetFlags( (WORD) iFlags, dwValue, NULL, TRUE );
|
|
if ( pszOut )
|
|
{
|
|
sprintf( szBuffer, "Value = %8X, pAddr = %8X\n", dwValue, (DWORD_PTR)pAddr );
|
|
Print( szBuffer );
|
|
Print( pszOut );
|
|
Print( "\n" );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************\
|
|
* Procedure: Itest
|
|
*
|
|
* Description: Tests the basic stdexts macros and functions - a good check
|
|
* on the debugger extensions in general before you waste time debuging
|
|
* entensions.
|
|
*
|
|
* Returns: fSuccess
|
|
*
|
|
* 11/4/1997 Created cdturner
|
|
*
|
|
\************************************************************************/
|
|
BOOL Itest()
|
|
{
|
|
Print("Print test!\n");
|
|
SAFEWHILE(TRUE)
|
|
{
|
|
Print("SAFEWHILE test... Hit Ctrl-C NOW!\n");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************\
|
|
* Procedure: Iver
|
|
*
|
|
* Description: Dumps versions of extensions and winsrv/win32k
|
|
*
|
|
* Returns: fSuccess
|
|
*
|
|
* 11/4/1997 Created cdturner
|
|
*
|
|
\************************************************************************/
|
|
BOOL Iver()
|
|
{
|
|
#if DEBUG
|
|
Print("SHLEXTS version: Debug.\n");
|
|
#else
|
|
Print("SHLEXTS version: Retail.\n");
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/************************************************************************\
|
|
*
|
|
* DumpVerboseFileInfo
|
|
*
|
|
* Stolen from MSDN.
|
|
*
|
|
\************************************************************************/
|
|
|
|
typedef struct LANGANDCODEPAGE {
|
|
WORD wLang;
|
|
WORD wCP;
|
|
} LANGANDCODEPAGE;
|
|
|
|
void DumpVersionString(LPVOID pBlock, LANGANDCODEPAGE *lpTranslate, LPCSTR pszKey)
|
|
{
|
|
char szBuf[128];
|
|
LPSTR pszValue;
|
|
DWORD cb;
|
|
|
|
wsprintfA(szBuf, "\\StringFileInfo\\%04x%04x\\%s",
|
|
lpTranslate->wLang, lpTranslate->wCP, pszKey);
|
|
if (VerQueryValueA(pBlock, szBuf, (LPVOID*)&pszValue, &cb) &&
|
|
lstrlenA(pszValue)) // lstrlen traps exceptions
|
|
{
|
|
Print(szBuf+16); // skip over "\\StringFileInfo\\"
|
|
Print(" = ");
|
|
Print(pszValue);
|
|
Print("\n");
|
|
}
|
|
}
|
|
|
|
LPCSTR c_rgszVersionKeys[] =
|
|
{
|
|
"CompanyName",
|
|
"FileDescription",
|
|
"InternalName",
|
|
"OriginalFilename",
|
|
"ProductName",
|
|
"ProductVersion",
|
|
"FileVersion",
|
|
"LegalCopyright",
|
|
"LegalTrademarks",
|
|
"PrivateBuild",
|
|
"SpecialBuild",
|
|
"Comments",
|
|
NULL,
|
|
};
|
|
|
|
void DumpVerboseFileInfo(LPVOID pBlock)
|
|
{
|
|
LANGANDCODEPAGE *lpTranslate;
|
|
DWORD cbTranslate;
|
|
|
|
// Read the list of languages and code pages
|
|
if (VerQueryValueA(pBlock, "\\VarFileInfo\\Translation",
|
|
(LPVOID*)&lpTranslate, &cbTranslate))
|
|
{
|
|
UINT i;
|
|
for (i = 0; i < cbTranslate/sizeof(*lpTranslate) && !IsCtrlCHit(); i++)
|
|
{
|
|
LPCSTR *ppszVK;
|
|
for (ppszVK = c_rgszVersionKeys; *ppszVK && !IsCtrlCHit(); ppszVK++)
|
|
{
|
|
DumpVersionString(pBlock, &lpTranslate[i], *ppszVK);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/************************************************************************\
|
|
* Procedure: Ifilever
|
|
*
|
|
* Description: Dumps versions of extensions and winsrv/win32k
|
|
*
|
|
* Returns: fSuccess
|
|
*
|
|
* 11/4/1997 Created cdturner
|
|
*
|
|
\************************************************************************/
|
|
BOOL Ifilever( DWORD dwOpts,
|
|
LPSTR pszArgs )
|
|
{
|
|
HINSTANCE hDll = NULL;
|
|
DLLGETVERSIONPROC pGetVer = NULL;
|
|
DWORD dwHandle;
|
|
DWORD dwBlockLen;
|
|
LPVOID pBlock = NULL;
|
|
char szMessage[200];
|
|
BOOL fSkipLoad = FALSE;
|
|
|
|
|
|
if ( pszArgs == NULL || lstrlenA( pszArgs ) == 0 )
|
|
{
|
|
pszArgs = "Shell32.dll"; // default filename
|
|
}
|
|
|
|
if ( !dwOpts )
|
|
{
|
|
dwOpts = OFLAG(n); // default flags
|
|
}
|
|
|
|
Print("filever ");
|
|
Print(pszArgs);
|
|
Print("\n");
|
|
|
|
if ( dwOpts & OFLAG(d) )
|
|
{
|
|
hDll = LoadLibraryA(pszArgs);
|
|
if ( hDll == NULL )
|
|
{
|
|
Print("LoadLibrary failed\n");
|
|
}
|
|
else
|
|
{
|
|
pGetVer = (DLLGETVERSIONPROC) GetProcAddress( hDll, "DllGetVersion");
|
|
if ( pGetVer )
|
|
{
|
|
DLLVERSIONINFO rgVerInfo;
|
|
|
|
rgVerInfo.cbSize = sizeof( rgVerInfo );
|
|
|
|
pGetVer( &rgVerInfo );
|
|
|
|
wsprintfA( szMessage, "DllGetVersion\n Major = %d\n Minor = %d\n Build = %d\n",
|
|
rgVerInfo.dwMajorVersion, rgVerInfo.dwMinorVersion, rgVerInfo.dwBuildNumber );
|
|
|
|
Print(szMessage );
|
|
}
|
|
FreeLibrary( hDll );
|
|
}
|
|
}
|
|
|
|
if ( dwOpts & (OFLAG(n) | OFLAG(v)) )
|
|
{
|
|
// now test the normal version details...
|
|
dwBlockLen = GetFileVersionInfoSizeA( pszArgs, &dwHandle );
|
|
if ( dwBlockLen == 0 )
|
|
{
|
|
Print("GetFileVersionSize failed\n");
|
|
}
|
|
else
|
|
{
|
|
pBlock = LocalAlloc( LPTR, dwBlockLen );
|
|
if ( pBlock )
|
|
{
|
|
if (GetFileVersionInfoA( pszArgs, dwHandle, dwBlockLen, pBlock ))
|
|
{
|
|
VS_FIXEDFILEINFO * pFileInfo;
|
|
UINT uLen;
|
|
|
|
VerQueryValueA( pBlock, "\\", (LPVOID *) &pFileInfo, &uLen );
|
|
Print("GetFileVersionInfo\n");
|
|
|
|
wsprintfA( szMessage, "Version: %d.%d.%d.%d (0x%08x`%08x)\n",
|
|
HIWORD(pFileInfo->dwFileVersionMS),
|
|
LOWORD(pFileInfo->dwFileVersionMS),
|
|
HIWORD(pFileInfo->dwFileVersionLS),
|
|
LOWORD(pFileInfo->dwFileVersionLS),
|
|
pFileInfo->dwFileVersionMS,
|
|
pFileInfo->dwFileVersionLS);
|
|
Print( szMessage );
|
|
}
|
|
|
|
if (dwOpts & OFLAG(v))
|
|
{
|
|
DumpVerboseFileInfo(pBlock);
|
|
}
|
|
LocalFree( pBlock );
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|