windows-nt/Source/XPSP1/NT/windows/appcompat/tools/runcompat/runcompat.cpp
2020-09-26 16:20:57 +08:00

722 lines
18 KiB
C++

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
runcompat.cpp
Abstract:
This app sets an environment variable that tells the compat system in Whistler to run
an app using a predefined set of fixes, called a "layer."
Usage is:
runcompat name_of_layer command_line
Example:
runcompat win95 notepad foo.txt
Created:
06/06/2000 dmunsil
Modified:
--*/
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
#include <stdlib.h>
#include "commdlg.h"
#include "resource.h"
extern "C" {
#include "shimdb.h"
}
// defines
const int MAX_COMMAND_LINE = 1000;
const int MAX_LAYER = 100;
// globals
WCHAR gszCommandLine[MAX_COMMAND_LINE] = L"";
WCHAR gszLayerName[MAX_LAYER] = L"";
BOOL gbEnableLog = FALSE;
BOOL gbDisableExisting = FALSE;
BOOL gbCreateRegistryStub = FALSE;
HINSTANCE ghInstance = NULL;
// forward function declarations
LRESULT CALLBACK DlgMain(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
void ViewFileLog(void);
void ClearFileLog(void);
void RunThatApp(void);
void GetNewCommandLine(HWND hDlg);
void EnumerateLayers(HWND hDlg);
void ParseFileLog(HWND hDlg);
LRESULT CALLBACK DlgParseLog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
void ShowHelp(void);
extern "C" int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow)
{
LPWSTR *pszCommandItems = NULL;
int nArgs = 0;
int i;
WCHAR *szExt;
DWORD dwLen = 0;
BOOL bShowUI = FALSE;
OSVERSIONINFO osvi;
ghInstance = hInstance;
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) {
//
// It runs on Win2k. Make sure to create the stub registry entry for
// the first EXE
//
gbCreateRegistryStub = TRUE;
}
if (!lpCmdLine || !lpCmdLine[0]) {
bShowUI = TRUE;
}
pszCommandItems = CommandLineToArgvW(lpCmdLine, &nArgs);
if (!pszCommandItems) {
bShowUI = TRUE;
} else {
if (pszCommandItems[0][0] == '-' || pszCommandItems[0][0] == '/') {
if (pszCommandItems[0][1] == '?' || pszCommandItems[0][1] == 'h' || pszCommandItems[0][1] == 'H') {
ShowHelp();
goto out;
}
}
if (nArgs <= 1) {
bShowUI = TRUE;
}
if (nArgs >= 1) {
wcscpy(gszLayerName, pszCommandItems[0]);
}
}
if (bShowUI) {
HWND hMainDlg = CreateDialog(hInstance, (LPCTSTR)IDD_MAIN, NULL, (DLGPROC)DlgMain);
MSG msg;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hMainDlg, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} else {
if (gszLayerName[0] == '!') {
// strip the bang, even though we'll put it back later,
// just so we disconnect the input syntax from the output syntax. That way we
// can change one or the other separately.
memmove(gszLayerName, gszLayerName + 1, wcslen(gszLayerName) * sizeof(WCHAR));
gbDisableExisting = TRUE;
} else {
gbDisableExisting = FALSE;
}
wcscpy(gszCommandLine, pszCommandItems[1]);
for (i = 2; i < nArgs; ++i) {
wcscat(gszCommandLine, L" ");
wcscat(gszCommandLine, pszCommandItems[i]);
}
RunThatApp();
}
out:
if (pszCommandItems) {
GlobalFree((HGLOBAL)pszCommandItems);
}
return 0;
}
// Message handler for main dialog.
LRESULT CALLBACK DlgMain(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
switch (message)
{
case WM_INITDIALOG:
{
CheckDlgButton(hDlg, IDC_CHECK_LOG, BST_CHECKED);
EnumerateLayers(hDlg);
HWND hEdit = GetDlgItem(hDlg, IDC_EDIT_COMMAND_LINE);
if (hEdit) {
SetFocus(hEdit);
}
// we set the focus manually, so we can return FALSE here.
return FALSE;
}
break;
case WM_COMMAND:
switch LOWORD(wParam) {
case IDC_BUTTON_VIEW_LOG:
ViewFileLog();
break;
case IDC_BUTTON_CLEAR_LOG:
ClearFileLog();
break;
case IDC_BUTTON_PARSE_LOG:
DialogBox(ghInstance, MAKEINTRESOURCE(IDD_LOG_INFO), hDlg, (DLGPROC)DlgParseLog);
break;
case IDC_BUTTON_BROWSE:
GetNewCommandLine(hDlg);
break;
case IDC_BUTTON_HELP:
ShowHelp();
break;
case IDOK:
{
int nSel;
GetDlgItemText(hDlg, IDC_EDIT_COMMAND_LINE, gszCommandLine, MAX_COMMAND_LINE);
gbEnableLog = (IsDlgButtonChecked(hDlg, IDC_CHECK_LOG) == BST_CHECKED);
gbDisableExisting = (IsDlgButtonChecked(hDlg, IDC_CHECK_DISABLE_EXISTING) == BST_CHECKED);
nSel = (int)SendDlgItemMessage(hDlg, IDC_LIST_LAYER, LB_GETCURSEL, 0, 0);
if (nSel != LB_ERR) {
if (LB_ERR != SendDlgItemMessage(hDlg, IDC_LIST_LAYER, LB_GETTEXT, nSel, (LPARAM)gszLayerName)) {
RunThatApp();
}
}
}
break;
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
PostQuitMessage(0);
return TRUE;
break;
}
}
return FALSE;
}
#define APPCOMPAT_KEY L"System\\CurrentControlSet\\Control\\Session Manager\\AppCompatibility"
void
AddRegistryStubForExe(
void)
{
BOOL bBraket = FALSE;
WCHAR* pwsz = gszCommandLine;
WCHAR wszExeName[128];
while (*pwsz == L' ' || *pwsz == L'\t') {
pwsz++;
}
while (*pwsz != 0) {
if (*pwsz == L'\"') {
bBraket = !bBraket;
} else if (*pwsz == L' ' && !bBraket) {
break;
}
pwsz++;
}
//
// Now walk back to get the caracters
//
pwsz--;
if (*pwsz == L'\"') {
pwsz--;
}
WCHAR* pwszEnd;
WCHAR* pwszStart = gszCommandLine;
pwszEnd = pwsz + 1;
while (pwsz >= gszCommandLine) {
if (*pwsz == L'\\') {
pwszStart = pwsz + 1;
break;
}
pwsz--;
}
memcpy(wszExeName, pwszStart, (pwszEnd - pwszStart) * sizeof(WCHAR));
wszExeName[pwszEnd - pwszStart] = 0;
WCHAR wszKey[256];
HKEY hkey;
DWORD type;
DWORD cbData = 0;
swprintf(wszKey, L"%s\\%s", APPCOMPAT_KEY, wszExeName);
if (RegCreateKeyW(HKEY_LOCAL_MACHINE, wszKey, &hkey) != ERROR_SUCCESS) {
// DPF_Log((eDbgLevelError, "Failed to open/create the appcompat key \"%s\"", szKey));
} else {
if (RegQueryValueExW(hkey, L"DllPatch-x", NULL, &type, NULL, &cbData) != ERROR_SUCCESS) {
BYTE data[16] = {0x0c, 0, 0, 0, 0, 0, 0, 0,
0x06, 0, 0, 0, 0, 0, 0, 0};
//
// The value doesn't exist. Create it.
//
RegSetValueExW(hkey,
L"y",
NULL,
REG_BINARY,
data,
sizeof(data));
data[0] = 0;
RegSetValueExW(hkey,
L"DllPatch-y",
NULL,
REG_SZ,
data,
2);
}
}
RegCloseKey(hkey);
}
void RunThatApp(void)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
SHELLEXECUTEINFO ShellExecuteInfo;
WCHAR szLayer[MAX_LAYER];
BOOL bSuccess = TRUE;
int nExt = 0;
WCHAR *szExt = NULL;
// put a bang on the front if we're supposed to disable the existing
// shims in the database
if (gbDisableExisting) {
wcscpy(szLayer, L"!");
} else {
szLayer[0] = 0;
}
wcscat(szLayer, gszLayerName);
SetEnvironmentVariable(L"__COMPAT_LAYER", szLayer);
if (gbEnableLog) {
SetEnvironmentVariable(L"SHIM_FILE_LOG", L"shim.log");
} else {
SetEnvironmentVariable(L"SHIM_FILE_LOG", NULL);
}
if (gbCreateRegistryStub) {
AddRegistryStubForExe();
}
// if the whole command line is a shell link, use
// ShellExecuteEx, but otherwise use CreateProcess
// for its simpler command-line handling
nExt = wcslen(gszCommandLine) - 4;
szExt = gszCommandLine + nExt;
if (nExt > 0 && _wcsicmp(szExt, L".lnk") == 0) {
ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo));
ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo);
ShellExecuteInfo.fMask = SEE_MASK_FLAG_NO_UI;
ShellExecuteInfo.lpVerb = L"open";
ShellExecuteInfo.lpFile = gszCommandLine;
ShellExecuteInfo.nShow = SW_SHOW;
bSuccess = ShellExecuteEx(&ShellExecuteInfo);
} else {
//
// try to get the starting directory
//
LPWSTR *argv;
int argc;
WCHAR szWorkingBuffer[MAX_PATH];
WCHAR *pszWorkingDir = NULL;
//
// try to get the starting directory
//
argv = CommandLineToArgvW(gszCommandLine, &argc);
if (argv && argv[0] && argv[0][0] && argc) {
//
// we only set the working directory if they give us a full path.
//
if (argv[0][1] == L':' || argv[0][1] == L'\\') {
//
// get the working directory, if possible
//
WCHAR *szTemp = wcsrchr(argv[0], L'\\');
if (szTemp) {
wcsncpy(szWorkingBuffer, argv[0], szTemp - argv[0]);
szWorkingBuffer[szTemp - argv[0]] = 0;
pszWorkingDir = szWorkingBuffer;
}
}
GlobalFree(argv);
argv = NULL;
}
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
bSuccess = TRUE;
if (!CreateProcess(NULL,
gszCommandLine,
NULL,
NULL,
FALSE,
0,
NULL,
pszWorkingDir,
&StartupInfo,
&ProcessInfo)) {
bSuccess = FALSE;
}
}
if (!bSuccess) {
DWORD dwErr;
WCHAR szMsg[1000];
WCHAR szErr[1000];
dwErr = GetLastError();
if (!FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
szErr,
999,
NULL )) {
wcscpy(szErr, L"(unknown)");
}
wsprintf(szMsg, L"Error 0x%08X:\n\n%s\nwhile executing command line \"%s\".", dwErr, szErr, gszCommandLine);
MessageBox(NULL, szMsg, L"Error", MB_OK | MB_ICONEXCLAMATION);
}
SetEnvironmentVariable(L"__COMPAT_LAYER", NULL);
SetEnvironmentVariable(L"SHIM_FILE_LOG", NULL);
}
void GetNewCommandLine(HWND hDlg)
{
OPENFILENAME ofn;
WCHAR szFullPath[1000];
szFullPath[0] = L'\0';
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hDlg;
ofn.lpstrFile = szFullPath;
ofn.nMaxFile = sizeof(szFullPath)/sizeof(szFullPath[0]);
ofn.lpstrFilter = L"Exe\0*.EXE\0All\0*.*\0";
ofn.nFilterIndex = 1;
ofn.lpstrTitle = L"Select EXE to run";
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NODEREFERENCELINKS;
// get the matching file name
if (GetOpenFileName(&ofn) == FALSE) {
return;
}
if (wcschr(szFullPath, L' ')) {
//
// if there are spaces in the path, quote the whole thing.
//
WCHAR szFullPathTemp[1002];
swprintf(szFullPathTemp, L"\"%s\"", szFullPath);
wcscpy(szFullPath, szFullPathTemp);
}
SetDlgItemText(hDlg, IDC_EDIT_COMMAND_LINE, szFullPath);
}
void ViewFileLog(void)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
WCHAR szCommand[1000];
// make sure we aren't shimming Notepad.
SetEnvironmentVariable(L"__COMPAT_LAYER", NULL);
SetEnvironmentVariable(L"SHIM_FILE_LOG", NULL);
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
ExpandEnvironmentStringsW(L"notepad %windir%\\AppPatch\\shim.log", szCommand, 1000);
CreateProcess(NULL,
szCommand,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&StartupInfo,
&ProcessInfo);
}
void ClearFileLog(void)
{
WCHAR szPath[1000];
WCHAR szMsg[1000];
ExpandEnvironmentStringsW(L"%windir%\\AppPatch\\shim.log", szPath, 1000);
DeleteFile(szPath);
wsprintf(szMsg, L"Deleted file \"%s\".", szPath);
MessageBox(NULL, szMsg, L"Cleared Log", MB_OK);
}
void EnumerateLayers(HWND hDlg)
{
PDB pdb = NULL;
WCHAR wszPath[MAX_PATH];
HWND hList;
TAGID tiDatabase;
TAGID tiLayer;
hList = GetDlgItem(hDlg, IDC_LIST_LAYER);
if (!hList) {
goto out;
}
ExpandEnvironmentStringsW(L"%windir%\\AppPatch\\sysmain.sdb", wszPath, MAX_PATH);
pdb = SdbOpenDatabase(wszPath, DOS_PATH);
if (!pdb) {
goto out;
}
tiDatabase = SdbFindFirstTag(pdb, TAGID_ROOT, TAG_DATABASE);
if (!tiDatabase) {
goto out;
}
tiLayer = SdbFindFirstTag(pdb, tiDatabase, TAG_LAYER);
while (tiLayer) {
TAGID tiName;
WCHAR wszName[MAX_PATH];
wszName[0] = 0;
tiName = SdbFindFirstTag(pdb, tiLayer, TAG_NAME);
if (tiName) {
SdbReadStringTag(pdb, tiName, wszName, MAX_PATH * sizeof(WCHAR));
}
if (wszName[0]) {
SendMessageW(hList, LB_ADDSTRING, 0, (LPARAM)wszName);
}
tiLayer = SdbFindNextTag(pdb, tiDatabase, tiLayer);
}
out:
if (pdb) {
SdbCloseDatabase(pdb);
}
// select the first item that begins with "Win9" or the first one in the list, if
// none match.
if (LB_ERR == SendMessageW(hList, LB_SELECTSTRING, -1, (LPARAM)L"Win9")) {
SendMessageW(hList, LB_SETCURSEL, 0, 0);
}
}
void ParseFileLog(HWND hDlg)
{
WCHAR szPath[1000];
WCHAR szLine[1000];
WCHAR szDate[50];
WCHAR szTime[50];
WCHAR szDll[200];
DWORD dwLevel = 0;
int nLast = 0;
static WCHAR szHeader[10000];
static WCHAR szWarnings[10000];
static WCHAR szErrors[10000];
static WCHAR szOther[10000];
FILE *file = NULL;
BOOL bInHeader = FALSE;
ExpandEnvironmentStringsW(L"%windir%\\AppPatch\\shim.log", szPath, 1000);
file = _wfopen(szPath, L"rt");
if (!file) {
goto out;
}
while (fgetws(szLine, 1000, file)) {
if (wcsncmp(szLine, L"----", 4) == 0) {
bInHeader = !bInHeader;
if (bInHeader) {
// we just started a new header, so clear everything
szHeader[0] = 0;
szErrors[0] = 0;
szWarnings[0] = 0;
szOther[0] = 0;
}
continue;
}
// remove the terminator(s)
nLast = wcslen(szLine) - 1;
while (nLast >= 0 && (szLine[nLast] == L'\n'|| szLine[nLast] == L'\r')) {
szLine[nLast] = L'\0';
nLast--;
}
if (bInHeader) {
wcscat(szHeader, szLine);
wcscat(szHeader, L"\r\n");
continue;
}
dwLevel = 0;
swscanf(szLine, L"%s %s %s %d", szDate, szTime, szDll, &dwLevel);
_wcslwr(szDll);
if (!wcsstr(szDll, L".dll") || dwLevel == 0) {
WCHAR szError[1000];
swprintf(szError, L"Unrecognized data in line: %s", szLine);
OutputDebugStringW(szError);
continue;
}
if (dwLevel == 1) {
if (!wcsstr(szErrors, szDll)) {
wcscat(szErrors, szDll);
wcscat(szErrors, L"\r\n");
}
} else if (dwLevel == 2) {
if (!wcsstr(szWarnings, szDll)) {
wcscat(szWarnings, szDll);
wcscat(szWarnings, L"\r\n");
}
} else {
if (!wcsstr(szOther, szDll)) {
wcscat(szOther, szDll);
wcscat(szOther, L"\r\n");
}
}
}
SetDlgItemText(hDlg, IDC_EDIT_HEADER, szHeader);
SetDlgItemText(hDlg, IDC_EDIT_ERRORS, szErrors);
SetDlgItemText(hDlg, IDC_EDIT_WARNINGS, szWarnings);
SetDlgItemText(hDlg, IDC_EDIT_OTHER, szOther);
out:
if (file) {
fclose(file);
}
return;
}
// Message handler for parse dialog.
LRESULT CALLBACK DlgParseLog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
switch (message)
{
case WM_INITDIALOG:
ParseFileLog(hDlg);
break;
case WM_COMMAND:
switch LOWORD(wParam) {
case IDOK:
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
break;
}
}
return FALSE;
}
void ShowHelp(void)
{
MessageBox(NULL, L"Apply a compatibility layer in addition to any fixes in the database:\n\n"
L" runcompat (layer name) (command line)\n\n"
L"Apply a compatibility layer and disable any existing fixes:\n\n"
L" runcompat !(layer name) (command line)\n\n"
L"Run GUI version:\n\n"
L" runcompat", L"Runcompat Command-Line Usage", MB_OK);
}