mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1382 lines
35 KiB
1382 lines
35 KiB
|
|
#include "precomp.h"
|
|
|
|
#include "dbsupport.h"
|
|
#include "viewlog.h"
|
|
|
|
|
|
#define AV_OPTIONS_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\AppVerifier"
|
|
|
|
#define AV_OPTION_CLEAR_LOG L"ClearLogsBeforeRun"
|
|
#define AV_OPTION_BREAK_ON_LOG L"BreakOnLog"
|
|
#define AV_OPTION_FULL_PAGEHEAP L"FullPageHeap"
|
|
#define AV_OPTION_AV_DEBUGGER L"UseAVDebugger"
|
|
|
|
|
|
//
|
|
// Forward declarations
|
|
//
|
|
void RefreshSettingsList(HWND hDlg);
|
|
|
|
CWinApp theApp;
|
|
|
|
|
|
HINSTANCE g_hInstance = NULL;
|
|
|
|
BOOL g_bSettingsDirty = FALSE;
|
|
|
|
BOOL g_bRefreshingSettings = FALSE;
|
|
|
|
BOOL g_bConsoleMode = FALSE;
|
|
|
|
//
|
|
// AppVerifier options
|
|
//
|
|
BOOL g_bClearSessionLogBeforeRun;
|
|
BOOL g_bBreakOnLog;
|
|
BOOL g_bFullPageHeap;
|
|
BOOL g_bUseAVDebugger;
|
|
|
|
// forward function declarations
|
|
LRESULT CALLBACK DlgMain(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
|
|
|
|
BOOL
|
|
GetAppTitleString(
|
|
wstring &strTitle
|
|
)
|
|
{
|
|
wstring strVersion;
|
|
|
|
if (!AVLoadString(IDS_APP_NAME, strTitle)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!AVLoadString(IDS_VERSION_STRING, strVersion)) {
|
|
return FALSE;
|
|
}
|
|
|
|
strTitle += L" ";
|
|
strTitle += strVersion;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
SearchGroupForSID(
|
|
DWORD dwGroup,
|
|
BOOL* pfIsMember
|
|
)
|
|
{
|
|
PSID pSID;
|
|
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
|
|
BOOL fRes = TRUE;
|
|
|
|
if (!AllocateAndInitializeSid(&SIDAuth,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
dwGroup,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
&pSID)) {
|
|
fRes = FALSE;
|
|
}
|
|
|
|
if (!CheckTokenMembership(NULL, pSID, pfIsMember)) {
|
|
fRes = FALSE;
|
|
}
|
|
|
|
FreeSid(pSID);
|
|
|
|
return fRes;
|
|
}
|
|
|
|
BOOL
|
|
CanRun(
|
|
void
|
|
)
|
|
{
|
|
BOOL fIsAdmin;
|
|
|
|
if (!SearchGroupForSID(DOMAIN_ALIAS_RID_ADMINS, &fIsAdmin))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return fIsAdmin;
|
|
}
|
|
|
|
void
|
|
DumpCurrentSettingsToConsole(void)
|
|
{
|
|
CAVAppInfo *pApp;
|
|
|
|
printf("\n");
|
|
printf("Current Verifier Settings:\n\n");
|
|
|
|
for (pApp = g_aAppInfo.begin(); pApp != g_aAppInfo.end(); pApp++) {
|
|
printf("%ls:\n", pApp->wstrExeName.c_str());
|
|
|
|
CTestInfo *pTest;
|
|
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (pApp->IsTestActive(*pTest)) {
|
|
printf(" %ls\n", pTest->strTestCommandLine.c_str());
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
printf("Done.\n");
|
|
printf("\n");
|
|
}
|
|
|
|
void
|
|
DumpHelpToConsole(void)
|
|
{
|
|
printf("\n");
|
|
printf("Usage: appverif.exe [flags] [tests] [APP [APP...]]\n");
|
|
printf("\n");
|
|
printf("No command-line: run appverif.exe in GUI mode.\n");
|
|
printf("\n");
|
|
printf("Flags:\n");
|
|
printf(" /? - print this help text.\n");
|
|
printf(" /querysettings (/q) - dump current settings to console.\n");
|
|
printf(" /reset (/r) - reset (clear) all settings for all apps.\n");
|
|
printf(" /all (/a) - enable all tests for specified apps.\n");
|
|
printf(" /default (/d) - enable default tests for specified apps.\n");
|
|
printf(" /none (/n) - disable all tests for specified apps.\n");
|
|
printf("\n");
|
|
printf("Tests (prefix with '+' to add and '-' to remove):\n");
|
|
printf("\n");
|
|
printf(" Kernel Tests:\n");
|
|
|
|
CTestInfo *pTest;
|
|
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (pTest->eTestType == TEST_KERNEL) {
|
|
printf(" %ls\n", pTest->strTestCommandLine.c_str());
|
|
}
|
|
}
|
|
printf("\n");
|
|
printf(" Shim Tests:\n");
|
|
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (pTest->eTestType == TEST_SHIM) {
|
|
printf(" %ls\n", pTest->strTestCommandLine.c_str());
|
|
}
|
|
}
|
|
|
|
printf("\n");
|
|
printf("(For descriptions of tests, run appverif.exe in GUI mode.)\n");
|
|
printf("\n");
|
|
printf("Examples:\n");
|
|
printf("\n");
|
|
printf(" appverif /d -pageheap foo.exe\n");
|
|
printf(" (turn on default tests except pageheap for foo.exe)\n");
|
|
printf("\n");
|
|
printf(" appverif /a -locks foo.exe\n");
|
|
printf(" (turn on all tests except locks for foo.exe)\n");
|
|
printf("\n");
|
|
printf(" appverif +pageheap foo.exe bar.exe\n");
|
|
printf(" (turn on just pageheap for foo.exe & bar.exe)\n");
|
|
printf("\n");
|
|
printf(" appverif /n foo.exe\n");
|
|
printf(" (clear all tests for foo.exe)\n");
|
|
printf("\n");
|
|
printf(" appverif /r\n");
|
|
printf(" (clear all tests for all apps)\n");
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
void
|
|
HandleCommandLine(int argc, LPWSTR *argv)
|
|
{
|
|
WCHAR szApp[MAX_PATH];
|
|
wstring strTemp;
|
|
CWStringArray astrApps;
|
|
|
|
szApp[0] = 0;
|
|
|
|
g_bConsoleMode = TRUE;
|
|
|
|
//
|
|
// print the title
|
|
//
|
|
if (GetAppTitleString(strTemp)) {
|
|
printf("\n%ls\n", strTemp.c_str());
|
|
}
|
|
if (AVLoadString(IDS_COPYRIGHT, strTemp)) {
|
|
printf("%ls\n\n", strTemp.c_str());
|
|
}
|
|
|
|
//
|
|
// check for global operations
|
|
//
|
|
if (_wcsnicmp(argv[0], L"/q", 2) == 0) { // querysettings
|
|
DumpCurrentSettingsToConsole();
|
|
return;
|
|
}
|
|
if (_wcsicmp(argv[0], L"/?") == 0) { // help
|
|
DumpHelpToConsole();
|
|
return;
|
|
}
|
|
if (_wcsnicmp(argv[0], L"/r", 2) == 0) { // reset
|
|
g_aAppInfo.clear();
|
|
goto out;
|
|
}
|
|
|
|
//
|
|
// first get a list of the app names
|
|
//
|
|
for (int nArg = 0 ; nArg != argc; nArg++) {
|
|
WCHAR wc = argv[nArg][0];
|
|
|
|
if (wc != L'/' && wc != L'-' && wc != L'+') {
|
|
astrApps.push_back(argv[nArg]);
|
|
}
|
|
}
|
|
|
|
if (astrApps.size() == 0) {
|
|
AVErrorResourceFormat(IDS_NO_APP);
|
|
DumpHelpToConsole();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// now for each app name, parse the list and adjust its settings
|
|
//
|
|
for (wstring *pStr = astrApps.begin(); pStr != astrApps.end(); pStr++) {
|
|
CAVAppInfo *pApp;
|
|
BOOL bFound = FALSE;
|
|
|
|
//
|
|
// check to see if they submitted a full path
|
|
//
|
|
const WCHAR * pExe = NULL;
|
|
const WCHAR * pPath = NULL;
|
|
|
|
pExe = wcsrchr(pStr->c_str(), L'\\');
|
|
if (!pExe) {
|
|
if ((*pStr)[1] == L':') {
|
|
pExe = pStr->c_str() + 2;
|
|
}
|
|
} else {
|
|
pExe++;
|
|
}
|
|
|
|
if (pExe) {
|
|
pPath = pStr->c_str();
|
|
} else {
|
|
pExe = pStr->c_str();
|
|
}
|
|
|
|
//
|
|
// first, find or add the app to the list, and get a pointer to it
|
|
//
|
|
for (pApp = g_aAppInfo.begin(); pApp != g_aAppInfo.end(); pApp++) {
|
|
if (_wcsicmp(pApp->wstrExeName.c_str(), pExe) == 0) {
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!bFound) {
|
|
CAVAppInfo App;
|
|
|
|
App.wstrExeName = pExe;
|
|
g_aAppInfo.push_back(App);
|
|
pApp = g_aAppInfo.end() - 1;
|
|
}
|
|
|
|
//
|
|
// if they submitted a full path, update the records
|
|
//
|
|
if (pPath) {
|
|
pApp->wstrExePath = pPath;
|
|
}
|
|
|
|
//
|
|
// now walk the command line again and make the adjustments
|
|
//
|
|
for (int nArg = 0 ; nArg != argc; nArg++) {
|
|
if (argv[nArg][0] == L'/') {
|
|
if (_wcsnicmp(argv[nArg], L"/a", 2) == 0) { // all
|
|
|
|
for (CTestInfo *pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
pApp->AddTest(*pTest);
|
|
}
|
|
} else if (_wcsnicmp(argv[nArg], L"/n", 2) == 0) { // none
|
|
|
|
for (CTestInfo *pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
pApp->RemoveTest(*pTest);
|
|
}
|
|
} else if (_wcsnicmp(argv[nArg], L"/d", 2) == 0) { // default
|
|
|
|
for (CTestInfo *pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (pTest->bDefault) {
|
|
pApp->AddTest(*pTest);
|
|
} else {
|
|
pApp->RemoveTest(*pTest);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// unknown parameter
|
|
//
|
|
AVErrorResourceFormat(IDS_INVALID_PARAMETER, argv[nArg]);
|
|
DumpHelpToConsole();
|
|
return;
|
|
}
|
|
|
|
} else if (argv[nArg][0] == L'+' || argv[nArg][0] == L'-') {
|
|
|
|
BOOL bAdd = (argv[nArg][0] == L'+');
|
|
LPWSTR szParam = argv[nArg] + 1;
|
|
|
|
//
|
|
// see if it's a shim name
|
|
//
|
|
CTestInfo *pTest;
|
|
BOOL bFound = FALSE;
|
|
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (_wcsicmp(szParam, pTest->strTestCommandLine.c_str()) == 0) {
|
|
if (bAdd) {
|
|
pApp->AddTest(*pTest);
|
|
} else {
|
|
pApp->RemoveTest(*pTest);
|
|
}
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFound) {
|
|
//
|
|
// unknown test
|
|
//
|
|
|
|
AVErrorResourceFormat(IDS_INVALID_TEST, szParam);
|
|
DumpHelpToConsole();
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// anything that doesn't begin with a slash, plus, or minus
|
|
// is an app name, so we'll ignore it
|
|
//
|
|
|
|
}
|
|
}
|
|
|
|
out:
|
|
//
|
|
// save them to disk/registry
|
|
//
|
|
SetCurrentAppSettings();
|
|
|
|
//
|
|
// show them the current settings, for verification
|
|
//
|
|
DumpCurrentSettingsToConsole();
|
|
}
|
|
|
|
|
|
extern "C" int APIENTRY
|
|
wWinMain(
|
|
HINSTANCE hInstance,
|
|
HINSTANCE hPrevInstance,
|
|
LPWSTR lpCmdLine,
|
|
int nCmdShow
|
|
)
|
|
{
|
|
LPWSTR* argv = NULL;
|
|
int argc = 0;
|
|
|
|
g_hInstance = hInstance;
|
|
|
|
InitTestInfo();
|
|
|
|
GetCurrentAppSettings();
|
|
|
|
if (lpCmdLine && lpCmdLine[0]) {
|
|
argv = CommandLineToArgvW(lpCmdLine, &argc);
|
|
}
|
|
|
|
//
|
|
// See if it's used as a debugger.
|
|
//
|
|
if (argc == 2 && _wcsicmp(argv[0], L"/debug") == 0) {
|
|
FreeConsole();
|
|
DebugApp(argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
if (!CanRun()) {
|
|
AVErrorResourceFormat(IDS_ACCESS_IS_DENIED);
|
|
return 0;
|
|
}
|
|
|
|
if (argc > 0) {
|
|
//
|
|
// we're in console mode, so handle everything as a console
|
|
//
|
|
HandleCommandLine(argc, argv);
|
|
return 1;
|
|
}
|
|
|
|
FreeConsole();
|
|
|
|
InitCommonControls();
|
|
|
|
HWND hMainDlg = CreateDialog(g_hInstance, (LPCTSTR)IDD_DLG_MAIN, NULL, (DLGPROC)DlgMain);
|
|
|
|
MSG msg;
|
|
|
|
//
|
|
// Main message loop:
|
|
//
|
|
while (GetMessage(&msg, NULL, 0, 0)) {
|
|
if (!IsDialogMessage(hMainDlg, &msg)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
RefreshAppList(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
CAVAppInfoArray::iterator it;
|
|
|
|
HWND hList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
|
|
ListView_DeleteAllItems(hList);
|
|
|
|
for (it = g_aAppInfo.begin(); it != g_aAppInfo.end(); it++) {
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.pszText = (LPWSTR)it->wstrExeName.c_str();
|
|
lvi.lParam = (LPARAM)it;
|
|
lvi.iItem = 9999;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_InsertItem(hList, &lvi);
|
|
}
|
|
|
|
RefreshSettingsList(hDlg);
|
|
}
|
|
|
|
void
|
|
DirtySettings(
|
|
HWND hDlg,
|
|
BOOL bDirty
|
|
)
|
|
{
|
|
g_bSettingsDirty = bDirty;
|
|
|
|
if (hDlg) {
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_SAVE_SETTINGS), bDirty);
|
|
}
|
|
}
|
|
|
|
void
|
|
SaveSettings(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
DirtySettings(hDlg, FALSE);
|
|
|
|
SetCurrentAppSettings();
|
|
}
|
|
|
|
void
|
|
SaveSettingsIfDirty(HWND hDlg)
|
|
{
|
|
if (g_bSettingsDirty) {
|
|
SaveSettings(hDlg);
|
|
}
|
|
}
|
|
|
|
void
|
|
DisplayLog(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
g_szSingleLogFile[0] = 0;
|
|
|
|
DialogBox(g_hInstance, (LPCTSTR)IDD_VIEWLOG_PAGE, hDlg, (DLGPROC)DlgViewLog);
|
|
}
|
|
|
|
void
|
|
DisplaySingleLog(HWND hDlg)
|
|
{
|
|
WCHAR wszFilter[] = L"Log files (*.log)\0*.log\0";
|
|
OPENFILENAME ofn;
|
|
WCHAR wszAppFullPath[MAX_PATH];
|
|
WCHAR wszAppShortName[MAX_PATH];
|
|
|
|
wstring wstrLogTitle;
|
|
|
|
if (!AVLoadString(IDS_VIEW_EXPORTED_LOG_TITLE, wstrLogTitle)) {
|
|
wstrLogTitle = _T("View Exported Log");
|
|
}
|
|
|
|
wszAppFullPath[0] = 0;
|
|
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = hDlg;
|
|
ofn.hInstance = NULL;
|
|
ofn.lpstrFilter = wszFilter;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nMaxCustFilter = 0;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrFile = wszAppFullPath;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = wszAppShortName;
|
|
ofn.nMaxFileTitle = MAX_PATH;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = wstrLogTitle.c_str();
|
|
ofn.Flags = OFN_PATHMUSTEXIST |
|
|
OFN_HIDEREADONLY | // hide the "open read-only" checkbox
|
|
OFN_NONETWORKBUTTON | // no network button
|
|
OFN_NOTESTFILECREATE | // don't test for write protection, a full disk, etc.
|
|
OFN_SHAREAWARE; // don't check the existance of file with OpenFile
|
|
ofn.lpstrDefExt = _T("log");
|
|
|
|
if ( !GetOpenFileName(&ofn) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
wcscpy(g_szSingleLogFile, wszAppFullPath);
|
|
|
|
DialogBox(g_hInstance, (LPCTSTR)IDD_VIEWLOG_PAGE, hDlg, (DLGPROC)DlgViewLog);
|
|
|
|
g_szSingleLogFile[0] = 0;
|
|
}
|
|
|
|
void
|
|
SelectApp(
|
|
HWND hDlg,
|
|
int nWhich
|
|
)
|
|
{
|
|
HWND hList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
|
|
int nItems = ListView_GetItemCount(hList);
|
|
|
|
if (nItems == 0) {
|
|
return;
|
|
}
|
|
|
|
if (nWhich > nItems - 1) {
|
|
nWhich = nItems - 1;
|
|
}
|
|
|
|
ListView_SetItemState(hList, nWhich, LVIS_SELECTED, LVIS_SELECTED);
|
|
}
|
|
|
|
void
|
|
RunSelectedApp(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
WCHAR wszCommandLine[256];
|
|
|
|
SaveSettings(hDlg);
|
|
|
|
HWND hAppList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
|
|
int nApp = ListView_GetNextItem(hAppList, -1, LVNI_SELECTED);
|
|
|
|
if (nApp == -1) {
|
|
return;
|
|
}
|
|
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nApp;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hAppList, &lvi);
|
|
|
|
CAVAppInfo *pApp = (CAVAppInfo*)lvi.lParam;
|
|
|
|
if (pApp->wstrExePath.size()) {
|
|
|
|
LPWSTR pwsz;
|
|
|
|
wcscpy(wszCommandLine, pApp->wstrExePath.c_str());
|
|
|
|
pwsz = wcsrchr(wszCommandLine, L'\\');
|
|
|
|
if (pwsz) {
|
|
*pwsz = 0;
|
|
SetCurrentDirectory(wszCommandLine);
|
|
*pwsz = L'\\';
|
|
}
|
|
|
|
} else {
|
|
wcscpy(wszCommandLine, pApp->wstrExeName.c_str());
|
|
}
|
|
|
|
PROCESS_INFORMATION ProcessInfo;
|
|
BOOL bRet;
|
|
STARTUPINFO StartupInfo;
|
|
|
|
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
|
|
StartupInfo.cb = sizeof(StartupInfo);
|
|
|
|
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
|
|
|
|
bRet = CreateProcess(NULL,
|
|
wszCommandLine,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&StartupInfo,
|
|
&ProcessInfo);
|
|
|
|
if (!bRet) {
|
|
WCHAR wszFilter[] = L"Executable files (*.exe)\0*.exe\0";
|
|
OPENFILENAME ofn;
|
|
WCHAR wszAppFullPath[MAX_PATH];
|
|
WCHAR wszAppShortName[MAX_PATH];
|
|
|
|
wcscpy(wszAppFullPath, wszCommandLine);
|
|
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = hDlg;
|
|
ofn.hInstance = NULL;
|
|
ofn.lpstrFilter = wszFilter;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nMaxCustFilter = 0;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrFile = wszAppFullPath;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = wszAppShortName;
|
|
ofn.nMaxFileTitle = MAX_PATH;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = _T("Please locate application");
|
|
ofn.Flags = OFN_PATHMUSTEXIST |
|
|
OFN_HIDEREADONLY | // hide the "open read-only" checkbox
|
|
OFN_NONETWORKBUTTON | // no network button
|
|
OFN_NOTESTFILECREATE | // don't test for write protection, a full disk, etc.
|
|
OFN_SHAREAWARE; // don't check the existance of file with OpenFile
|
|
ofn.lpstrDefExt = NULL;
|
|
|
|
if (!GetOpenFileName(&ofn)) {
|
|
return;
|
|
}
|
|
|
|
pApp->wstrExePath = wszAppFullPath;
|
|
pApp->wstrExeName = wszAppShortName;
|
|
wcscpy(wszCommandLine, pApp->wstrExePath.c_str());
|
|
|
|
RefreshAppList(hDlg);
|
|
|
|
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
|
|
StartupInfo.cb = sizeof(StartupInfo);
|
|
|
|
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
|
|
|
|
bRet = CreateProcess(NULL,
|
|
wszCommandLine,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&StartupInfo,
|
|
&ProcessInfo);
|
|
if (!bRet) {
|
|
AVErrorResourceFormat(IDS_CANT_LAUNCH_EXE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
AddAppToList(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
|
|
WCHAR wszFilter[] = L"Executable files (*.exe)\0*.exe\0";
|
|
OPENFILENAME ofn;
|
|
WCHAR wszAppFullPath[MAX_PATH];
|
|
WCHAR wszAppShortName[MAX_PATH];
|
|
|
|
wstring wstrTitle;
|
|
|
|
if (!AVLoadString(IDS_ADD_APPLICATION_TITLE, wstrTitle)) {
|
|
wstrTitle = _T("Add Application");
|
|
}
|
|
|
|
wszAppFullPath[0] = 0;
|
|
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = hDlg;
|
|
ofn.hInstance = NULL;
|
|
ofn.lpstrFilter = wszFilter;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nMaxCustFilter = 0;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrFile = wszAppFullPath;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = wszAppShortName;
|
|
ofn.nMaxFileTitle = MAX_PATH;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = wstrTitle.c_str();
|
|
ofn.Flags = OFN_HIDEREADONLY | // hide the "open read-only" checkbox
|
|
OFN_NONETWORKBUTTON | // no network button
|
|
OFN_NOTESTFILECREATE | // don't test for write protection, a full disk, etc.
|
|
OFN_SHAREAWARE; // don't check the existance of file with OpenFile
|
|
ofn.lpstrDefExt = _T("EXE");
|
|
|
|
if (!GetOpenFileName(&ofn)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// check to see if the app is already in the list
|
|
//
|
|
CAVAppInfo *pApp;
|
|
BOOL bFound = FALSE;
|
|
|
|
for (pApp = g_aAppInfo.begin(); pApp != g_aAppInfo.end(); pApp++) {
|
|
if (_wcsicmp(pApp->wstrExeName.c_str(), wszAppShortName) == 0) {
|
|
//
|
|
// the app is already in the list, so just update the full
|
|
// path, if it's good
|
|
//
|
|
if (GetFileAttributes(wszAppFullPath) != -1) {
|
|
pApp->wstrExePath = wszAppFullPath;
|
|
}
|
|
bFound = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// if the app wasn't already in the list, add it to the list
|
|
//
|
|
if (!bFound) {
|
|
CAVAppInfo AppInfo;
|
|
|
|
AppInfo.wstrExeName = wszAppShortName;
|
|
|
|
//
|
|
// check to see if the file actually exists
|
|
//
|
|
if (GetFileAttributes(wszAppFullPath) != -1) {
|
|
AppInfo.wstrExePath = wszAppFullPath;
|
|
}
|
|
|
|
//
|
|
// init the default tests
|
|
//
|
|
CTestInfo *pTest;
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
if (pTest->bDefault) {
|
|
AppInfo.AddTest(*pTest);
|
|
}
|
|
}
|
|
|
|
g_aAppInfo.push_back(AppInfo);
|
|
|
|
RefreshAppList(hDlg);
|
|
|
|
SelectApp(hDlg, 9999);
|
|
|
|
DirtySettings(hDlg, TRUE);
|
|
}
|
|
}
|
|
|
|
void
|
|
RemoveSelectedApp(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
HWND hAppList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
|
|
int nApp = ListView_GetNextItem(hAppList, -1, LVNI_SELECTED);
|
|
|
|
if (nApp == -1) {
|
|
return;
|
|
}
|
|
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nApp;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hAppList, &lvi);
|
|
|
|
CAVAppInfo *pApp = (CAVAppInfo*)lvi.lParam;
|
|
|
|
g_aAppInfo.erase(pApp);
|
|
|
|
RefreshAppList(hDlg);
|
|
|
|
SelectApp(hDlg, nApp);
|
|
|
|
DirtySettings(hDlg, TRUE);
|
|
}
|
|
|
|
void
|
|
ScanSettingsList(
|
|
HWND hDlg,
|
|
int nItem
|
|
)
|
|
{
|
|
|
|
HWND hSettingList = GetDlgItem(hDlg, IDC_LIST_SETTINGS);
|
|
HWND hAppList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
int nBegin, nEnd;
|
|
|
|
int nApp = ListView_GetNextItem(hAppList, -1, LVNI_SELECTED);
|
|
|
|
if (nApp == -1) {
|
|
return;
|
|
}
|
|
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nApp;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hAppList, &lvi);
|
|
|
|
CAVAppInfo *pApp = (CAVAppInfo*)lvi.lParam;
|
|
|
|
if (!pApp) {
|
|
return;
|
|
}
|
|
|
|
int nItems = ListView_GetItemCount(hSettingList);
|
|
|
|
if (!nItems) {
|
|
//
|
|
// nothing in list
|
|
//
|
|
return;
|
|
}
|
|
|
|
if (nItem == -1 || nItem >= nItems) {
|
|
nBegin = 0;
|
|
nEnd = nItems;
|
|
} else {
|
|
nBegin = nItem;
|
|
nEnd = nItem + 1;
|
|
}
|
|
|
|
for (int i = nBegin; i < nEnd; ++i) {
|
|
BOOL bTestEnabled = FALSE;
|
|
BOOL bChecked = FALSE;
|
|
|
|
lvi.iItem = i;
|
|
|
|
ListView_GetItem(hSettingList, &lvi);
|
|
|
|
CTestInfo *pTest = (CTestInfo*)lvi.lParam;
|
|
|
|
bChecked = ListView_GetCheckState(hSettingList, i);
|
|
|
|
bTestEnabled = pApp->IsTestActive(*pTest);
|
|
|
|
if (bTestEnabled != bChecked) {
|
|
DirtySettings(hDlg, TRUE);
|
|
|
|
if (bChecked) {
|
|
pApp->AddTest(*pTest);
|
|
} else {
|
|
pApp->RemoveTest(*pTest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DisplaySettingsDescription(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
HWND hList = GetDlgItem(hDlg, IDC_LIST_SETTINGS);
|
|
|
|
int nItem = ListView_GetNextItem(hList, -1, LVNI_SELECTED);
|
|
|
|
if (nItem == -1) {
|
|
WCHAR szTestDesc[256];
|
|
|
|
LoadString(g_hInstance, IDS_VIEW_TEST_DESC, szTestDesc, 256);
|
|
SetWindowText(GetDlgItem(hDlg, IDC_STATIC_DESC), szTestDesc);
|
|
} else {
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nItem;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hList, &lvi);
|
|
|
|
CTestInfo *pTest = (CTestInfo*)lvi.lParam;
|
|
|
|
SetWindowText(GetDlgItem(hDlg, IDC_STATIC_DESC), pTest->strTestDescription.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
RefreshSettingsList(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
HWND hSettingList = GetDlgItem(hDlg, IDC_LIST_SETTINGS);
|
|
HWND hAppList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
|
|
static nLastItem = -1;
|
|
|
|
int nItem = ListView_GetNextItem(hAppList, -1, LVNI_SELECTED);
|
|
|
|
if (nItem == -1) {
|
|
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_RUN), FALSE);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_REMOVE), FALSE);
|
|
|
|
} else {
|
|
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_RUN), TRUE);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_REMOVE), TRUE);
|
|
}
|
|
|
|
if (nItem == nLastItem) {
|
|
return;
|
|
|
|
}
|
|
|
|
ListView_DeleteAllItems(hSettingList);
|
|
|
|
DisplaySettingsDescription(hDlg);
|
|
|
|
nLastItem = nItem;
|
|
|
|
if (nItem != -1) {
|
|
LVITEM lvi;
|
|
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nItem;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hAppList, &lvi);
|
|
|
|
CAVAppInfo *pApp = (CAVAppInfo*)lvi.lParam;
|
|
|
|
if (!pApp) {
|
|
return;
|
|
}
|
|
|
|
CTestInfo* pTest;
|
|
|
|
for (pTest = g_aTestInfo.begin(); pTest != g_aTestInfo.end(); pTest++) {
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.pszText = (LPWSTR)pTest->strTestName.c_str();
|
|
lvi.lParam = (LPARAM)pTest;
|
|
lvi.iItem = 9999;
|
|
lvi.iSubItem = 0;
|
|
|
|
int nItem = ListView_InsertItem(hSettingList, &lvi);
|
|
|
|
BOOL bCheck = pApp->IsTestActive(*pTest);
|
|
|
|
ListView_SetCheckState(hSettingList, nItem, bCheck);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ReadOptions(
|
|
void
|
|
)
|
|
{
|
|
LONG lRet;
|
|
HKEY hKey;
|
|
DWORD cbData;
|
|
|
|
lRet = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
|
|
AV_OPTIONS_KEY,
|
|
0,
|
|
NULL,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&hKey,
|
|
NULL);
|
|
|
|
if (lRet != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
cbData = sizeof(DWORD);
|
|
|
|
RegQueryValueEx(hKey,
|
|
AV_OPTION_CLEAR_LOG,
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&g_bClearSessionLogBeforeRun,
|
|
&cbData);
|
|
|
|
RegQueryValueEx(hKey,
|
|
AV_OPTION_BREAK_ON_LOG,
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&g_bBreakOnLog,
|
|
&cbData);
|
|
|
|
RegQueryValueEx(hKey,
|
|
AV_OPTION_FULL_PAGEHEAP,
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&g_bFullPageHeap,
|
|
&cbData);
|
|
|
|
RegQueryValueEx(hKey,
|
|
AV_OPTION_AV_DEBUGGER,
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&g_bUseAVDebugger,
|
|
&cbData);
|
|
|
|
RegCloseKey(hKey);
|
|
}
|
|
|
|
void
|
|
SaveOptions(
|
|
void
|
|
)
|
|
{
|
|
LONG lRet;
|
|
HKEY hKey;
|
|
|
|
lRet = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
|
|
AV_OPTIONS_KEY,
|
|
0,
|
|
NULL,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&hKey,
|
|
NULL);
|
|
|
|
if (lRet != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
RegSetValueEx(hKey,
|
|
AV_OPTION_CLEAR_LOG,
|
|
NULL,
|
|
REG_DWORD,
|
|
(BYTE*)&g_bClearSessionLogBeforeRun,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueEx(hKey,
|
|
AV_OPTION_BREAK_ON_LOG,
|
|
NULL,
|
|
REG_DWORD,
|
|
(BYTE*)&g_bBreakOnLog,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueEx(hKey,
|
|
AV_OPTION_FULL_PAGEHEAP,
|
|
NULL,
|
|
REG_DWORD,
|
|
(BYTE*)&g_bFullPageHeap,
|
|
sizeof(DWORD));
|
|
|
|
RegSetValueEx(hKey,
|
|
AV_OPTION_AV_DEBUGGER,
|
|
NULL,
|
|
REG_DWORD,
|
|
(BYTE*)&g_bUseAVDebugger,
|
|
sizeof(DWORD));
|
|
|
|
RegCloseKey(hKey);
|
|
SetCurrentAppSettings();
|
|
}
|
|
|
|
|
|
LRESULT CALLBACK
|
|
DlgViewOptions(
|
|
HWND hDlg,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
ReadOptions();
|
|
SendDlgItemMessage(hDlg,
|
|
IDC_CLEAR_LOG_ON_CHANGES,
|
|
BM_SETCHECK,
|
|
(g_bClearSessionLogBeforeRun ? BST_CHECKED : BST_UNCHECKED),
|
|
0);
|
|
|
|
SendDlgItemMessage(hDlg,
|
|
IDC_BREAK_ON_LOG,
|
|
BM_SETCHECK,
|
|
(g_bBreakOnLog ? BST_CHECKED : BST_UNCHECKED),
|
|
0);
|
|
|
|
SendDlgItemMessage(hDlg,
|
|
IDC_FULL_PAGEHEAP,
|
|
BM_SETCHECK,
|
|
(g_bFullPageHeap ? BST_CHECKED : BST_UNCHECKED),
|
|
0);
|
|
|
|
SendDlgItemMessage(hDlg,
|
|
IDC_USE_AV_DEBUGGER,
|
|
BM_SETCHECK,
|
|
(g_bUseAVDebugger ? BST_CHECKED : BST_UNCHECKED),
|
|
0);
|
|
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam)) {
|
|
case IDOK:
|
|
g_bClearSessionLogBeforeRun = (SendDlgItemMessage(hDlg,
|
|
IDC_CLEAR_LOG_ON_CHANGES,
|
|
BM_GETCHECK,
|
|
0,
|
|
0) == BST_CHECKED);
|
|
|
|
g_bBreakOnLog = (SendDlgItemMessage(hDlg,
|
|
IDC_BREAK_ON_LOG,
|
|
BM_GETCHECK,
|
|
0,
|
|
0) == BST_CHECKED);
|
|
|
|
g_bFullPageHeap = (SendDlgItemMessage(hDlg,
|
|
IDC_FULL_PAGEHEAP,
|
|
BM_GETCHECK,
|
|
0,
|
|
0) == BST_CHECKED);
|
|
|
|
g_bUseAVDebugger = (SendDlgItemMessage(hDlg,
|
|
IDC_USE_AV_DEBUGGER,
|
|
BM_GETCHECK,
|
|
0,
|
|
0) == BST_CHECKED);
|
|
|
|
SaveOptions();
|
|
|
|
EndDialog(hDlg, LOWORD(wParam));
|
|
return TRUE;
|
|
|
|
case IDCANCEL:
|
|
EndDialog(hDlg, LOWORD(wParam));
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
ViewOptions(
|
|
HWND hDlg
|
|
)
|
|
{
|
|
DialogBox(g_hInstance, (LPCTSTR)IDD_OPTIONS, hDlg, (DLGPROC)DlgViewOptions);
|
|
}
|
|
|
|
// Message handler for main dialog.
|
|
LRESULT CALLBACK
|
|
DlgMain(
|
|
HWND hDlg,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
{
|
|
wstring strTemp;
|
|
|
|
//
|
|
// set the caption to the appropriate version, etc.
|
|
//
|
|
if (GetAppTitleString(strTemp)) {
|
|
SetWindowText(hDlg, strTemp.c_str());
|
|
}
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_RUN), FALSE);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_REMOVE), FALSE);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_BTN_SAVE_SETTINGS), FALSE);
|
|
|
|
HWND hList = GetDlgItem(hDlg, IDC_LIST_SETTINGS);
|
|
|
|
if (hList) {
|
|
LVCOLUMN lvc;
|
|
|
|
lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;
|
|
lvc.fmt = LVCFMT_LEFT;
|
|
lvc.cx = 300;
|
|
lvc.iSubItem = 0;
|
|
lvc.pszText = L"xxx";
|
|
|
|
ListView_InsertColumn(hList, 0, &lvc);
|
|
ListView_SetExtendedListViewStyleEx(hList, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
|
|
|
|
}
|
|
|
|
hList = GetDlgItem(hDlg, IDC_LIST_APPS);
|
|
if (hList) {
|
|
LVITEM lvi;
|
|
LVCOLUMN lvc;
|
|
|
|
lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH;
|
|
lvc.fmt = LVCFMT_LEFT;
|
|
lvc.cx = 250;
|
|
lvc.iSubItem = 0;
|
|
lvc.pszText = L"xxx";
|
|
|
|
ListView_InsertColumn(hList, 0, &lvc);
|
|
|
|
RefreshAppList(hDlg);
|
|
|
|
SelectApp(hDlg, 0);
|
|
}
|
|
|
|
WCHAR szTestDesc[256];
|
|
|
|
LoadString(g_hInstance, IDS_VIEW_TEST_DESC, szTestDesc, 256);
|
|
SetWindowText(GetDlgItem(hDlg, IDC_STATIC_DESC), szTestDesc);
|
|
|
|
//
|
|
// Show the app icon.
|
|
//
|
|
HICON hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON));
|
|
|
|
SetClassLongPtr(hDlg, GCLP_HICON, (LONG_PTR)hIcon);
|
|
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_ACTIVATE:
|
|
if (LOWORD(wParam) == WA_INACTIVE) {
|
|
SaveSettingsIfDirty(hDlg);
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wParam)) {
|
|
case IDC_BTN_ADD:
|
|
AddAppToList(hDlg);
|
|
break;
|
|
|
|
case IDC_BTN_REMOVE:
|
|
RemoveSelectedApp(hDlg);
|
|
break;
|
|
|
|
case IDC_BTN_VIEW_LOG:
|
|
DisplayLog(hDlg);
|
|
break;
|
|
|
|
case IDC_BTN_VIEW_EXTERNAL_LOG:
|
|
DisplaySingleLog(hDlg);
|
|
break;
|
|
|
|
case IDC_BTN_OPTIONS:
|
|
ViewOptions(hDlg);
|
|
break;
|
|
|
|
case IDC_BTN_RUN:
|
|
RunSelectedApp(hDlg);
|
|
break;
|
|
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
SaveSettings(hDlg);
|
|
EndDialog(hDlg, LOWORD(wParam));
|
|
PostQuitMessage(0);
|
|
return TRUE;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
LPNMHDR pnmh = (LPNMHDR)lParam;
|
|
|
|
HWND hItem = pnmh->hwndFrom;
|
|
|
|
if (hItem == GetDlgItem(hDlg, IDC_LIST_APPS)) {
|
|
switch (pnmh->code) {
|
|
case LVN_KEYDOWN:
|
|
{
|
|
LPNMLVKEYDOWN pnmkd = (LPNMLVKEYDOWN)lParam;
|
|
|
|
if (pnmkd->wVKey == VK_DELETE) {
|
|
if (IsWindowEnabled(GetDlgItem(hDlg, IDC_BTN_RUN))) {
|
|
RemoveSelectedApp(hDlg);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LVN_ITEMCHANGED:
|
|
LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
|
|
|
|
if (!g_bRefreshingSettings && (pnmv->uChanged & LVIF_STATE) && ((pnmv->uNewState ^ pnmv->uOldState) & LVIS_SELECTED)) {
|
|
g_bRefreshingSettings = TRUE;
|
|
|
|
RefreshSettingsList(hDlg);
|
|
|
|
g_bRefreshingSettings = FALSE;
|
|
}
|
|
|
|
}
|
|
} else if (hItem == GetDlgItem(hDlg, IDC_LIST_SETTINGS)) {
|
|
switch (pnmh->code) {
|
|
case LVN_ITEMCHANGED:
|
|
LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
|
|
|
|
if (!g_bRefreshingSettings) {
|
|
if ((pnmv->uChanged & LVIF_STATE) && ((pnmv->uNewState ^ pnmv->uOldState) & LVIS_SELECTED)) {
|
|
DisplaySettingsDescription(hDlg);
|
|
}
|
|
if ((pnmv->uChanged & LVIF_STATE) && ((pnmv->uNewState ^ pnmv->uOldState) >> 12) != 0) {
|
|
ScanSettingsList(hDlg, pnmv->iItem);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|