xref: /reactos/dll/appcompat/apphelp/apphelp.c (revision 4572aad1)
1 /*
2  * PROJECT:     ReactOS Application compatibility module
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     apphelp entrypoint / generic interface functions
5  * COPYRIGHT:   Copyright 2011 André Hentschel
6  *              Copyright 2013 Mislav Blaževic
7  *              Copyright 2015-2019 Mark Jansen (mark.jansen@reactos.org)
8  */
9 
10 #define WIN32_NO_STATUS
11 #include "windef.h"
12 #include "winbase.h"
13 #include "winreg.h"
14 #include "strsafe.h"
15 #include "apphelp.h"
16 #include <ndk/rtlfuncs.h>
17 #include <ndk/cmfuncs.h>
18 #include <ndk/obfuncs.h>
19 #include <ndk/kdtypes.h>
20 
21 
22 ACCESS_MASK Wow64QueryFlag(void);
23 
24 const UNICODE_STRING InstalledSDBKeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB");
25 
26 /* from dpfilter.h */
27 #define DPFLTR_APPCOMPAT_ID 123
28 
29 #define MAX_GUID_STRING_LEN     sizeof("{12345678-1234-1234-0123-456789abcdef}")
30 
31 #ifndef NT_SUCCESS
32 #define NT_SUCCESS(StatCode)  ((NTSTATUS)(StatCode) >= 0)
33 #endif
34 
35 ULONG g_ShimDebugLevel = ~0;
36 HMODULE g_hInstance;
37 
38 void ApphelppInitDebugLevel(void)
39 {
40     static const UNICODE_STRING DebugKey = RTL_CONSTANT_STRING(L"SHIM_DEBUG_LEVEL");
41     UNICODE_STRING DebugValue;
42     NTSTATUS Status;
43     ULONG NewLevel = SHIM_ERR;
44     WCHAR Buffer[40];
45 
46     RtlInitEmptyUnicodeString(&DebugValue, Buffer, sizeof(Buffer));
47 
48     Status = RtlQueryEnvironmentVariable_U(NULL, &DebugKey, &DebugValue);
49 
50     if (NT_SUCCESS(Status))
51     {
52         if (!NT_SUCCESS(RtlUnicodeStringToInteger(&DebugValue, 10, &NewLevel)))
53             NewLevel = SHIM_ERR;
54     }
55     g_ShimDebugLevel = NewLevel;
56 }
57 
58 
59 BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, LPVOID reserved )
60 {
61     switch (reason)
62     {
63     case DLL_PROCESS_ATTACH:
64         g_hInstance = hinst;
65         DisableThreadLibraryCalls(hinst);
66         SdbpHeapInit();
67         break;
68     case DLL_PROCESS_DETACH:
69         SdbpHeapDeinit();
70         break;
71     }
72     return TRUE;
73 }
74 
75 BOOL WINAPI ApphelpCheckInstallShieldPackage(void* ptr, LPCWSTR path)
76 {
77     SHIM_WARN("stub: ptr=%p, path='%S'\n", ptr, path);
78     return TRUE;
79 }
80 
81 
82 BOOL WINAPI ApphelpCheckShellObject(REFCLSID ObjectCLSID, BOOL bShimIfNecessary, ULONGLONG *pullFlags)
83 {
84     WCHAR GuidString[MAX_GUID_STRING_LEN];
85     if (!ObjectCLSID || !SdbGUIDToString(ObjectCLSID, GuidString, RTL_NUMBER_OF(GuidString)))
86         GuidString[0] = L'\0';
87     SHIM_WARN("stub: ObjectCLSID='%S', bShimIfNecessary=%d, pullFlags=%p)\n", GuidString, bShimIfNecessary, pullFlags);
88 
89     if (pullFlags)
90         *pullFlags = 0;
91 
92     return TRUE;
93 }
94 
95 /**
96  * Outputs diagnostic info.
97  *
98  * @param [in]  Level           The level to log this message with, choose any of [SHIM_ERR,
99  *                              SHIM_WARN, SHIM_INFO].
100  * @param [in]  FunctionName    The function this log should be attributed to.
101  * @param [in]  Format          The format string.
102  * @param   ...                 Variable arguments providing additional information.
103  *
104  * @return  Success: TRUE Failure: FALSE.
105  */
106 BOOL WINAPIV ShimDbgPrint(SHIM_LOG_LEVEL Level, PCSTR FunctionName, PCSTR Format, ...)
107 {
108     char Buffer[512];
109     va_list ArgList;
110     char* Current = Buffer;
111     const char* LevelStr;
112     size_t Length = sizeof(Buffer);
113 
114     if (g_ShimDebugLevel == ~0)
115         ApphelppInitDebugLevel();
116 
117     if (Level > g_ShimDebugLevel)
118         return FALSE;
119 
120     switch (Level)
121     {
122     case SHIM_ERR:
123         LevelStr = "Err ";
124         Level = DPFLTR_MASK | (1 << DPFLTR_ERROR_LEVEL);
125         break;
126     case SHIM_WARN:
127         LevelStr = "Warn";
128         Level = DPFLTR_MASK | (1 << DPFLTR_WARNING_LEVEL);
129         break;
130     case SHIM_INFO:
131         LevelStr = "Info";
132         Level = DPFLTR_MASK | (1 << DPFLTR_INFO_LEVEL);
133         break;
134     default:
135         LevelStr = "User";
136         Level = DPFLTR_MASK | (1 << DPFLTR_INFO_LEVEL);
137         break;
138     }
139     StringCchPrintfExA(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, "[%s][%-20s] ", LevelStr, FunctionName);
140 
141     va_start(ArgList, Format);
142     StringCchVPrintfExA(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, Format, ArgList);
143     va_end(ArgList);
144 
145 #if defined(APPCOMPAT_USE_DBGPRINTEX) && APPCOMPAT_USE_DBGPRINTEX
146     return NT_SUCCESS(DbgPrintEx(DPFLTR_APPCOMPAT_ID, Level, "%s", Buffer));
147 #else
148     DbgPrint("%s", Buffer);
149     return TRUE;
150 #endif
151 }
152 
153 
154 #define APPHELP_DONTWRITE_REASON    2
155 #define APPHELP_CLEARBITS           0x100   /* TODO: Investigate */
156 #define APPHELP_IGNORE_ENVIRONMENT  0x400
157 
158 #define APPHELP_VALID_RESULT        0x10000
159 #define APPHELP_RESULT_NOTFOUND     0x20000
160 #define APPHELP_RESULT_FOUND        0x40000
161 
162 /**
163  * Lookup Shims / Fixes for the specified application
164  *
165  * @param [in]  FileHandle                  Handle to the file to check.
166  * @param [in]  Unk1
167  * @param [in]  Unk2
168  * @param [in]  ApplicationName             Exe to check
169  * @param [in]  Environment                 The environment variables to use, or NULL to use the current environment.
170  * @param [in]  ExeType                     Exe type (MACHINE_TYPE_XXXX)
171  * @param [in,out]  Reason                  Input/output flags
172  * @param [in]  SdbQueryAppCompatData       The resulting data.
173  * @param [in]  SdbQueryAppCompatDataSize   The resulting data size.
174  * @param [in]  SxsData                     TODO
175  * @param [in]  SxsDataSize                 TODO
176  * @param [in]  FusionFlags                 TODO
177  * @param [in]  SomeFlag1                   TODO
178  * @param [in]  SomeFlag2                   TODO
179  *
180  * @return  TRUE if the application is allowed to run.
181  */
182 BOOL
183 WINAPI
184 ApphelpCheckRunAppEx(
185     _In_ HANDLE FileHandle,
186     _In_opt_ PVOID Unk1,
187     _In_opt_ PVOID Unk2,
188     _In_opt_z_ PCWSTR ApplicationName,
189     _In_opt_ PVOID Environment,
190     _In_opt_ USHORT ExeType,
191     _Inout_opt_ PULONG Reason,
192     _Out_opt_ PVOID* SdbQueryAppCompatData,
193     _Out_opt_ PULONG SdbQueryAppCompatDataSize,
194     _Out_opt_ PVOID* SxsData,
195     _Out_opt_ PULONG SxsDataSize,
196     _Out_opt_ PULONG FusionFlags,
197     _Out_opt_ PULONG64 SomeFlag1,
198     _Out_opt_ PULONG SomeFlag2)
199 {
200     SDBQUERYRESULT* result = NULL;
201     HSDB hsdb = NULL;
202     DWORD dwFlags = 0;
203 
204     if (SxsData)
205         *SxsData = NULL;
206     if (SxsDataSize)
207         *SxsDataSize = 0;
208     if (FusionFlags)
209         *FusionFlags = 0;
210     if (SomeFlag1)
211         *SomeFlag1 = 0;
212     if (SomeFlag2)
213         *SomeFlag2 = 0;
214     if (Reason)
215         dwFlags = *Reason;
216 
217     dwFlags &= ~APPHELP_CLEARBITS;
218 
219     *SdbQueryAppCompatData = result = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SDBQUERYRESULT));
220     if (SdbQueryAppCompatDataSize)
221         *SdbQueryAppCompatDataSize = sizeof(*result);
222 
223 
224     hsdb = SdbInitDatabase(HID_DOS_PATHS | SDB_DATABASE_MAIN_SHIM, NULL);
225     if (hsdb)
226     {
227         BOOL FoundMatch;
228         DWORD MatchingExeFlags = 0;
229 
230         if (dwFlags & APPHELP_IGNORE_ENVIRONMENT)
231             MatchingExeFlags |= SDBGMEF_IGNORE_ENVIRONMENT;
232 
233         FoundMatch = SdbGetMatchingExe(hsdb, ApplicationName, NULL, Environment, MatchingExeFlags, result);
234         if (FileHandle != INVALID_HANDLE_VALUE)
235         {
236             dwFlags |= APPHELP_VALID_RESULT;
237             dwFlags |= (FoundMatch ? APPHELP_RESULT_FOUND : APPHELP_RESULT_NOTFOUND);
238         }
239 
240         SdbReleaseDatabase(hsdb);
241     }
242 
243     if (Reason && !(dwFlags & APPHELP_DONTWRITE_REASON))
244         *Reason = dwFlags;
245 
246 
247     /* We should _ALWAYS_ return TRUE here, unless we want to block an application from starting! */
248     return TRUE;
249 }
250 
251 
252 /**
253  * @name SdbRegisterDatabaseEx
254  * Register an application compatibility database
255  *
256  * @param pszDatabasePath   The database. Required
257  * @param dwDatabaseType    The database type. SDB_DATABASE_*
258  * @param pTimeStamp        The timestamp. When this argument is not provided, the system time is used.
259  * @return                  TRUE on success, or FALSE on failure.
260  */
261 BOOL WINAPI SdbRegisterDatabaseEx(
262     _In_ LPCWSTR pszDatabasePath,
263     _In_ DWORD dwDatabaseType,
264     _In_opt_ const PULONGLONG pTimeStamp)
265 {
266     PDB pdb;
267     DB_INFORMATION Information;
268     WCHAR GuidBuffer[MAX_GUID_STRING_LEN];
269     UNICODE_STRING KeyName;
270     ACCESS_MASK KeyAccess;
271     OBJECT_ATTRIBUTES ObjectKey = RTL_INIT_OBJECT_ATTRIBUTES(&KeyName, OBJ_CASE_INSENSITIVE);
272     NTSTATUS Status;
273     HANDLE InstalledSDBKey;
274 
275     pdb = SdbOpenDatabase(pszDatabasePath, DOS_PATH);
276     if (!pdb)
277     {
278         SHIM_ERR("Unable to open DB %S\n", pszDatabasePath);
279         return FALSE;
280     }
281 
282     if (!SdbGetDatabaseInformation(pdb, &Information) ||
283         !(Information.dwFlags & DB_INFO_FLAGS_VALID_GUID))
284     {
285         SHIM_ERR("Unable to retrieve DB info\n");
286         SdbCloseDatabase(pdb);
287         return FALSE;
288     }
289 
290     if (!SdbGUIDToString(&Information.Id, GuidBuffer, RTL_NUMBER_OF(GuidBuffer)))
291     {
292         SHIM_ERR("Unable to Convert GUID to string\n");
293         SdbFreeDatabaseInformation(&Information);
294         SdbCloseDatabase(pdb);
295         return FALSE;
296     }
297 
298     KeyName = InstalledSDBKeyName;
299     KeyAccess = Wow64QueryFlag() | KEY_WRITE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS;
300     Status = NtCreateKey(&InstalledSDBKey, KeyAccess, &ObjectKey, 0, NULL, 0, NULL);
301     if (NT_SUCCESS(Status))
302     {
303         HANDLE DbKey;
304 
305         RtlInitUnicodeString(&KeyName, GuidBuffer);
306         ObjectKey.RootDirectory = InstalledSDBKey;
307 
308         Status = NtCreateKey(&DbKey, KeyAccess, &ObjectKey, 0, NULL, 0, NULL);
309         if (NT_SUCCESS(Status))
310         {
311             UNICODE_STRING DatabasePathKey = RTL_CONSTANT_STRING(L"DatabasePath");
312             UNICODE_STRING DatabaseInstallTimeStampKey = RTL_CONSTANT_STRING(L"DatabaseInstallTimeStamp");
313             UNICODE_STRING DatabaseTypeKey = RTL_CONSTANT_STRING(L"DatabaseType");
314             UNICODE_STRING DatabaseDescriptionKey = RTL_CONSTANT_STRING(L"DatabaseDescription");
315 
316             Status = NtSetValueKey(DbKey, &DatabasePathKey, 0, REG_SZ,
317                                    (PVOID)pszDatabasePath, ((ULONG)wcslen(pszDatabasePath) + 1) * sizeof(WCHAR));
318             if (!NT_SUCCESS(Status))
319                 SHIM_ERR("Unable to write %wZ\n", &DatabasePathKey);
320 
321             if (NT_SUCCESS(Status))
322             {
323                 ULARGE_INTEGER ulTimeStamp;
324                 if (pTimeStamp)
325                 {
326                     ulTimeStamp.QuadPart = *pTimeStamp;
327                 }
328                 else
329                 {
330                     FILETIME fi;
331                     GetSystemTimeAsFileTime(&fi);
332                     ulTimeStamp.LowPart = fi.dwLowDateTime;
333                     ulTimeStamp.HighPart = fi.dwHighDateTime;
334                 }
335                 Status = NtSetValueKey(DbKey, &DatabaseInstallTimeStampKey, 0, REG_QWORD,
336                                        &ulTimeStamp.QuadPart, sizeof(ulTimeStamp));
337                 if (!NT_SUCCESS(Status))
338                     SHIM_ERR("Unable to write %wZ\n", &DatabaseInstallTimeStampKey);
339             }
340 
341             if (NT_SUCCESS(Status))
342             {
343                 Status = NtSetValueKey(DbKey, &DatabaseTypeKey, 0, REG_DWORD,
344                                        &dwDatabaseType, sizeof(dwDatabaseType));
345                 if (!NT_SUCCESS(Status))
346                     SHIM_ERR("Unable to write %wZ\n", &DatabaseTypeKey);
347             }
348 
349             if (NT_SUCCESS(Status) && Information.Description)
350             {
351                 Status = NtSetValueKey(DbKey, &DatabaseDescriptionKey, 0, REG_SZ,
352                                        (PVOID)Information.Description, ((ULONG)wcslen(Information.Description) + 1) * sizeof(WCHAR));
353                 if (!NT_SUCCESS(Status))
354                     SHIM_ERR("Unable to write %wZ\n", &DatabaseDescriptionKey);
355             }
356 
357             NtClose(DbKey);
358 
359             if (NT_SUCCESS(Status))
360             {
361                 SHIM_INFO("Installed %wS as %wZ\n", pszDatabasePath, &KeyName);
362             }
363         }
364         else
365         {
366             SHIM_ERR("Unable to create key %wZ\n", &KeyName);
367         }
368 
369         NtClose(InstalledSDBKey);
370     }
371     else
372     {
373         SHIM_ERR("Unable to create key %wZ\n", &KeyName);
374     }
375 
376     SdbFreeDatabaseInformation(&Information);
377     SdbCloseDatabase(pdb);
378 
379     return NT_SUCCESS(Status);
380 }
381 
382 
383 /**
384  * @name SdbRegisterDatabase
385  * Register an application compatibility database
386  *
387  * @param pszDatabasePath   The database. Required
388  * @param dwDatabaseType    The database type. SDB_DATABASE_*
389  * @return                  TRUE on success, or FALSE on failure.
390  */
391 BOOL WINAPI SdbRegisterDatabase(
392     _In_ LPCWSTR pszDatabasePath,
393     _In_ DWORD dwDatabaseType)
394 {
395     return SdbRegisterDatabaseEx(pszDatabasePath, dwDatabaseType, NULL);
396 }
397 
398 
399 /**
400  * @name SdbUnregisterDatabase
401  *
402  *
403  * @param pguidDB
404  * @return
405  */
406 BOOL WINAPI SdbUnregisterDatabase(_In_ const GUID *pguidDB)
407 {
408     WCHAR KeyBuffer[MAX_PATH], GuidBuffer[50];
409     UNICODE_STRING KeyName;
410     ACCESS_MASK KeyAccess;
411     OBJECT_ATTRIBUTES ObjectKey = RTL_INIT_OBJECT_ATTRIBUTES(&KeyName, OBJ_CASE_INSENSITIVE);
412     NTSTATUS Status;
413     HANDLE DbKey;
414 
415     if (!SdbGUIDToString(pguidDB, GuidBuffer, RTL_NUMBER_OF(GuidBuffer)))
416     {
417         SHIM_ERR("Unable to Convert GUID to string\n");
418         return FALSE;
419     }
420 
421     RtlInitEmptyUnicodeString(&KeyName, KeyBuffer, sizeof(KeyBuffer));
422     RtlAppendUnicodeStringToString(&KeyName, &InstalledSDBKeyName);
423     RtlAppendUnicodeToString(&KeyName, L"\\");
424     RtlAppendUnicodeToString(&KeyName, GuidBuffer);
425 
426     KeyAccess = Wow64QueryFlag() | DELETE;
427     Status = NtCreateKey(&DbKey, KeyAccess, &ObjectKey, 0, NULL, 0, NULL);
428     if (!NT_SUCCESS(Status))
429     {
430         SHIM_ERR("Unable to open %wZ\n", &KeyName);
431         return FALSE;
432     }
433 
434     Status = NtDeleteKey(DbKey);
435     if (!NT_SUCCESS(Status))
436         SHIM_ERR("Unable to delete %wZ\n", &KeyName);
437 
438     NtClose(DbKey);
439     return NT_SUCCESS(Status);
440 }
441 
442 
443 /* kernel32.dll */
444 BOOL WINAPI BaseDumpAppcompatCache(VOID);
445 BOOL WINAPI BaseFlushAppcompatCache(VOID);
446 
447 
448 /**
449  * @name ShimDumpCache
450  * Dump contents of the shim cache.
451  *
452  * @param hwnd          Unused, pass 0
453  * @param hInstance     Unused, pass 0
454  * @param lpszCmdLine   Unused, pass 0
455  * @param nCmdShow      Unused, pass 0
456  * @return
457  */
458 BOOL WINAPI ShimDumpCache(HWND hwnd, HINSTANCE hInstance, LPCSTR lpszCmdLine, int nCmdShow)
459 {
460     return BaseDumpAppcompatCache();
461 }
462 
463 /**
464 * @name ShimFlushCache
465 * Flush the shim cache. Call this after installing a new shim database
466 *
467 * @param hwnd          Unused, pass 0
468 * @param hInstance     Unused, pass 0
469 * @param lpszCmdLine   Unused, pass 0
470 * @param nCmdShow      Unused, pass 0
471 * @return
472 */
473 BOOL WINAPI ShimFlushCache(HWND hwnd, HINSTANCE hInstance, LPCSTR lpszCmdLine, int nCmdShow)
474 {
475     return BaseFlushAppcompatCache();
476 }
477