[APPHELP] Implement Sdb[Un]packAppCompatData and SdbGetAppCompatDataSize. CORE-13284
[reactos.git] / reactos / dll / appcompat / apphelp / hsdb.c
1 /*
2 * Copyright 2011 André Hentschel
3 * Copyright 2013 Mislav Blažević
4 * Copyright 2015-2017 Mark Jansen
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #define WIN32_NO_STATUS
22 #include "windows.h"
23 #include "ntndk.h"
24 #include "strsafe.h"
25 #include "apphelp.h"
26
27 #include "wine/unicode.h"
28
29 #define MAX_LAYER_LENGTH 256
30 #define GPLK_USER 1
31 #define GPLK_MACHINE 2
32
33 typedef struct _ShimData
34 {
35 WCHAR szModule[MAX_PATH];
36 DWORD dwSize;
37 DWORD dwMagic;
38 SDBQUERYRESULT Query;
39 WCHAR szLayer[MAX_LAYER_LENGTH];
40 DWORD unknown; // 0x14c
41 } ShimData;
42
43 #define SHIMDATA_MAGIC 0xAC0DEDAB
44
45
46 static BOOL WINAPI SdbpFileExists(LPCWSTR path)
47 {
48 DWORD attr = GetFileAttributesW(path);
49 return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
50 }
51
52 static BOOL WINAPI SdbpMatchExe(PDB db, TAGID exe, WCHAR* dir)
53 {
54 static const WCHAR fmt[] = {'%','s','%','s',0};
55 WCHAR buffer[256]; /* FIXME: rewrite using a buffer that can grow if needed, f.e. RtlInitBuffer stuff! */
56 TAGID matching_file;
57
58 /* TODO: check size/checksum from the main exe as well as from the extra files */
59 for (matching_file = SdbFindFirstTag(db, exe, TAG_MATCHING_FILE);
60 matching_file != TAGID_NULL; matching_file = SdbFindNextTag(db, exe, matching_file))
61 {
62 TAGID tagName = SdbFindFirstTag(db, matching_file, TAG_NAME);
63 LPWSTR name = SdbGetStringTagPtr(db, tagName);
64
65 if (!wcscmp(name, L"*"))
66 {
67 // if attributes dont match main file, return FALSE!
68 continue;
69 }
70
71 snprintfW(buffer, _countof(buffer), fmt, dir, name);
72 if (!SdbpFileExists(buffer))
73 return FALSE;
74 }
75
76 return TRUE;
77 }
78
79 static void SdbpAddDatabaseGuid(PDB db, PSDBQUERYRESULT result)
80 {
81 size_t n;
82
83 for (n = 0; n < _countof(result->rgGuidDB); ++n)
84 {
85 if (!memcmp(&result->rgGuidDB[n], &db->database_id, sizeof(db->database_id)))
86 return;
87
88 if (result->dwCustomSDBMap & (1<<n))
89 continue;
90
91 memcpy(&result->rgGuidDB[n], &db->database_id, sizeof(result->rgGuidDB[n]));
92 result->dwCustomSDBMap |= (1<<n);
93 return;
94 }
95 }
96
97 static BOOL SdbpAddSingleLayerMatch(TAGREF layer, PSDBQUERYRESULT result)
98 {
99 size_t n;
100
101 for (n = 0; n < result->dwLayerCount; ++n)
102 {
103 if (result->atrLayers[n] == layer)
104 return FALSE;
105 }
106
107 if (n >= _countof(result->atrLayers))
108 return FALSE;
109
110 result->atrLayers[n] = layer;
111 result->dwLayerCount++;
112
113 return TRUE;
114 }
115
116
117 static BOOL SdbpAddNamedLayerMatch(HSDB hsdb, PCWSTR layerName, PSDBQUERYRESULT result)
118 {
119 TAGID database, layer;
120 TAGREF tr;
121 PDB db = hsdb->db;
122
123 database = SdbFindFirstTag(db, TAGID_ROOT, TAG_DATABASE);
124 if (database == TAGID_NULL)
125 return FALSE;
126
127 layer = SdbFindFirstNamedTag(db, database, TAG_LAYER, TAG_NAME, layerName);
128 if (layer == TAGID_NULL)
129 return FALSE;
130
131 if (!SdbTagIDToTagRef(hsdb, db, layer, &tr))
132 return FALSE;
133
134 if (!SdbpAddSingleLayerMatch(tr, result))
135 return FALSE;
136
137 SdbpAddDatabaseGuid(db, result);
138 return TRUE;
139 }
140
141 static void SdbpAddExeLayers(HSDB hsdb, PDB db, TAGID tagExe, PSDBQUERYRESULT result)
142 {
143 TAGID layer = SdbFindFirstTag(db, tagExe, TAG_LAYER);
144
145 while (layer != TAGID_NULL)
146 {
147 TAGREF tr;
148 TAGID layerIdTag = SdbFindFirstTag(db, layer, TAG_LAYER_TAGID);
149 DWORD tagId = SdbReadDWORDTag(db, layerIdTag, TAGID_NULL);
150
151 if (layerIdTag != TAGID_NULL &&
152 tagId != TAGID_NULL &&
153 SdbTagIDToTagRef(hsdb, db, tagId, &tr))
154 {
155 SdbpAddSingleLayerMatch(tr, result);
156 }
157 else
158 {
159 /* Try a name lookup */
160 TAGID layerTag = SdbFindFirstTag(db, layer, TAG_NAME);
161 if (layerTag != TAGID_NULL)
162 {
163 LPCWSTR layerName = SdbGetStringTagPtr(db, layerTag);
164 if (layerName)
165 {
166 SdbpAddNamedLayerMatch(hsdb, layerName, result);
167 }
168 }
169 }
170
171 layer = SdbFindNextTag(db, tagExe, layer);
172 }
173 }
174
175 static void SdbpAddExeMatch(HSDB hsdb, PDB db, TAGID tagExe, PSDBQUERYRESULT result)
176 {
177 size_t n;
178 TAGREF tr;
179
180 if (!SdbTagIDToTagRef(hsdb, db, tagExe, &tr))
181 return;
182
183 for (n = 0; n < result->dwExeCount; ++n)
184 {
185 if (result->atrExes[n] == tr)
186 return;
187 }
188
189 if (n >= _countof(result->atrExes))
190 return;
191
192 result->atrExes[n] = tr;
193 result->dwExeCount++;
194
195 SdbpAddExeLayers(hsdb, db, tagExe, result);
196
197 SdbpAddDatabaseGuid(db, result);
198 }
199
200 static ULONG SdbpAddLayerMatches(HSDB hsdb, PWSTR pwszLayers, DWORD pdwBytes, PSDBQUERYRESULT result)
201 {
202 PWSTR start = pwszLayers, p;
203 ULONG Added = 0;
204
205 const PWSTR end = pwszLayers + (pdwBytes / sizeof(WCHAR));
206 while (start < end && (*start == L'!' || *start == L'#' || *start == L' ' || *start == L'\t'))
207 start++;
208
209 if (start == end)
210 return 0;
211
212 do
213 {
214 while (*start == L' ' || *start == L'\t')
215 ++start;
216
217 if (*start == UNICODE_NULL)
218 break;
219 p = wcspbrk(start, L" \t");
220
221 if (p)
222 *p = UNICODE_NULL;
223
224 if (SdbpAddNamedLayerMatch(hsdb, start, result))
225 Added++;
226
227 start = p + 1;
228 } while (start < end && p);
229
230 return Added;
231 }
232
233 static BOOL SdbpPropagateEnvLayers(HSDB hsdb, LPWSTR Environment, PSDBQUERYRESULT Result)
234 {
235 static const UNICODE_STRING EnvKey = RTL_CONSTANT_STRING(L"__COMPAT_LAYER");
236 UNICODE_STRING EnvValue;
237 NTSTATUS Status;
238 WCHAR Buffer[MAX_LAYER_LENGTH];
239
240 RtlInitEmptyUnicodeString(&EnvValue, Buffer, sizeof(Buffer));
241
242 Status = RtlQueryEnvironmentVariable_U(Environment, &EnvKey, &EnvValue);
243
244 if (!NT_SUCCESS(Status))
245 return FALSE;
246
247 return SdbpAddLayerMatches(hsdb, Buffer, EnvValue.Length, Result) > 0;
248 }
249
250
251
252 /**
253 * Opens specified shim database file Handle returned by this function may only be used by
254 * functions which take HSDB param thus differing it from SdbOpenDatabase.
255 *
256 * @param [in] flags Specifies type of path or predefined database.
257 * @param [in] path Path to the shim database file.
258 *
259 * @return Success: Handle to the opened shim database, NULL otherwise.
260 */
261 HSDB WINAPI SdbInitDatabase(DWORD flags, LPCWSTR path)
262 {
263 static const WCHAR shim[] = {'\\','s','y','s','m','a','i','n','.','s','d','b',0};
264 static const WCHAR msi[] = {'\\','m','s','i','m','a','i','n','.','s','d','b',0};
265 static const WCHAR drivers[] = {'\\','d','r','v','m','a','i','n','.','s','d','b',0};
266 LPCWSTR name;
267 WCHAR buffer[128];
268 HSDB hsdb;
269
270 hsdb = SdbAlloc(sizeof(SDB));
271 if (!hsdb)
272 return NULL;
273 hsdb->auto_loaded = 0;
274
275 /* Check for predefined databases */
276 if ((flags & HID_DATABASE_TYPE_MASK) && path == NULL)
277 {
278 switch (flags & HID_DATABASE_TYPE_MASK)
279 {
280 case SDB_DATABASE_MAIN_SHIM: name = shim; break;
281 case SDB_DATABASE_MAIN_MSI: name = msi; break;
282 case SDB_DATABASE_MAIN_DRIVERS: name = drivers; break;
283 default:
284 SdbReleaseDatabase(hsdb);
285 return NULL;
286 }
287 SdbGetAppPatchDir(NULL, buffer, 128);
288 memcpy(buffer + lstrlenW(buffer), name, SdbpStrsize(name));
289 flags = HID_DOS_PATHS;
290 }
291
292 hsdb->db = SdbOpenDatabase(path ? path : buffer, (flags & 0xF) - 1);
293
294 /* If database could not be loaded, a handle doesn't make sense either */
295 if (!hsdb->db)
296 {
297 SdbReleaseDatabase(hsdb);
298 return NULL;
299 }
300
301 return hsdb;
302 }
303
304 /**
305 * Closes shim database opened by SdbInitDatabase.
306 *
307 * @param [in] hsdb Handle to the shim database.
308 */
309 void WINAPI SdbReleaseDatabase(HSDB hsdb)
310 {
311 SdbCloseDatabase(hsdb->db);
312 SdbFree(hsdb);
313 }
314
315 /**
316 * Queries database for a specified exe If hsdb is NULL default database shall be loaded and
317 * searched.
318 *
319 * @param [in] hsdb Handle to the shim database.
320 * @param [in] path Path to executable for which we query database.
321 * @param [in] module_name Unused.
322 * @param [in] env The environment block to use
323 * @param [in] flags 0 or SDBGMEF_IGNORE_ENVIRONMENT.
324 * @param [out] result Pointer to structure in which query result shall be stored.
325 *
326 * @return TRUE if it succeeds, FALSE if it fails.
327 */
328 BOOL WINAPI SdbGetMatchingExe(HSDB hsdb, LPCWSTR path, LPCWSTR module_name,
329 LPCWSTR env, DWORD flags, PSDBQUERYRESULT result)
330 {
331 BOOL ret = FALSE;
332 TAGID database, iter, name;
333 PATTRINFO attribs = NULL;
334 /*DWORD attr_count;*/
335 RTL_UNICODE_STRING_BUFFER DosApplicationName = { { 0 } };
336 WCHAR DosPathBuffer[MAX_PATH];
337 ULONG PathType = 0;
338 LPWSTR file_name;
339 WCHAR wszLayers[MAX_LAYER_LENGTH];
340 DWORD dwSize;
341 PDB db;
342
343 /* Load default database if one is not specified */
344 if (!hsdb)
345 {
346 /* To reproduce windows behaviour HID_DOS_PATHS needs
347 * to be specified when loading default database */
348 hsdb = SdbInitDatabase(HID_DOS_PATHS | SDB_DATABASE_MAIN_SHIM, NULL);
349 if (hsdb)
350 hsdb->auto_loaded = TRUE;
351 }
352
353 ZeroMemory(result, sizeof(*result));
354
355 /* No database could be loaded */
356 if (!hsdb || !path)
357 return FALSE;
358
359 /* We do not support multiple db's yet! */
360 db = hsdb->db;
361
362 RtlInitUnicodeString(&DosApplicationName.String, path);
363 RtlInitBuffer(&DosApplicationName.ByteBuffer, (PUCHAR)DosPathBuffer, sizeof(DosPathBuffer));
364 if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY, &DosApplicationName.ByteBuffer, DosApplicationName.String.MaximumLength)))
365 {
366 SHIM_ERR("Failed to convert allocate buffer.");
367 goto Cleanup;
368 }
369 /* Update the internal buffer to contain the string */
370 memcpy(DosApplicationName.ByteBuffer.Buffer, path, DosApplicationName.String.MaximumLength);
371 /* Make sure the string uses our internal buffer (we want to modify the buffer,
372 and RtlNtPathNameToDosPathName does not always modify the String to point to the Buffer)! */
373 DosApplicationName.String.Buffer = (PWSTR)DosApplicationName.ByteBuffer.Buffer;
374
375 if (!NT_SUCCESS(RtlNtPathNameToDosPathName(0, &DosApplicationName, &PathType, NULL)))
376 {
377 SHIM_ERR("Failed to convert %S to DOS Path.", path);
378 goto Cleanup;
379 }
380
381
382 /* Extract file name */
383 file_name = strrchrW(DosApplicationName.String.Buffer, '\\');
384 if (!file_name)
385 {
386 SHIM_ERR("Failed to find Exe name in %wZ.", &DosApplicationName.String);
387 goto Cleanup;
388 }
389
390 /* We will use the buffer for exe name and directory. */
391 *(file_name++) = UNICODE_NULL;
392
393 /* Get information about executable required to match it with database entry */
394 /*if (!SdbGetFileAttributes(path, &attribs, &attr_count))
395 return FALSE;*/
396
397 /* DATABASE is list TAG which contains all executables */
398 database = SdbFindFirstTag(db, TAGID_ROOT, TAG_DATABASE);
399 if (database == TAGID_NULL)
400 {
401 goto Cleanup;
402 }
403
404 /* EXE is list TAG which contains data required to match executable */
405 iter = SdbFindFirstTag(db, database, TAG_EXE);
406
407 /* Search for entry in database, we should look into indexing tags! */
408 while (iter != TAGID_NULL)
409 {
410 LPWSTR foundName;
411 /* Check if exe name matches */
412 name = SdbFindFirstTag(db, iter, TAG_NAME);
413 /* If this is a malformed DB, (no TAG_NAME), we should not crash. */
414 foundName = SdbGetStringTagPtr(db, name);
415 if (foundName && !lstrcmpiW(foundName, file_name))
416 {
417 /* We have a null terminator before the application name, so DosApplicationName only contains the path. */
418 if (SdbpMatchExe(db, iter, DosApplicationName.String.Buffer))
419 {
420 ret = TRUE;
421 SdbpAddExeMatch(hsdb, db, iter, result);
422 }
423 }
424
425 /* Continue iterating */
426 iter = SdbFindNextTag(db, database, iter);
427 }
428
429 /* Restore the full path. */
430 *(--file_name) = L'\\';
431
432 dwSize = sizeof(wszLayers);
433 if (SdbGetPermLayerKeys(DosApplicationName.String.Buffer, wszLayers, &dwSize, GPLK_MACHINE | GPLK_USER))
434 {
435 SdbpAddLayerMatches(hsdb, wszLayers, dwSize, result);
436 ret = TRUE;
437 }
438
439 if (!(flags & SDBGMEF_IGNORE_ENVIRONMENT))
440 {
441 if (SdbpPropagateEnvLayers(hsdb, (LPWSTR)env, result))
442 {
443 ret = TRUE;
444 result->dwFlags |= SHIMREG_HAS_ENVIRONMENT;
445 }
446 }
447
448 Cleanup:
449 RtlFreeBuffer(&DosApplicationName.ByteBuffer);
450 if (attribs)
451 SdbFreeFileAttributes(attribs);
452 if (hsdb->auto_loaded)
453 SdbReleaseDatabase(hsdb);
454 return ret;
455 }
456
457 /**
458 * Retrieves AppPatch directory.
459 *
460 * @param [in] db Handle to the shim database.
461 * @param [out] path Pointer to memory in which path shall be written.
462 * @param [in] size Size of the buffer in characters.
463 */
464 BOOL WINAPI SdbGetAppPatchDir(HSDB db, LPWSTR path, DWORD size)
465 {
466 static WCHAR* default_dir = NULL;
467 static CONST WCHAR szAppPatch[] = {'\\','A','p','p','P','a','t','c','h',0};
468
469 /* In case function fails, path holds empty string */
470 if (size > 0)
471 *path = 0;
472
473 if (!default_dir)
474 {
475 WCHAR* tmp = NULL;
476 UINT len = GetSystemWindowsDirectoryW(NULL, 0) + lstrlenW(szAppPatch);
477 tmp = SdbAlloc((len + 1)* sizeof(WCHAR));
478 if (tmp)
479 {
480 UINT r = GetSystemWindowsDirectoryW(tmp, len+1);
481 if (r && r < len)
482 {
483 if (SUCCEEDED(StringCchCatW(tmp, len+1, szAppPatch)))
484 {
485 if (InterlockedCompareExchangePointer((void**)&default_dir, tmp, NULL) == NULL)
486 tmp = NULL;
487 }
488 }
489 if (tmp)
490 SdbFree(tmp);
491 }
492 if (!default_dir)
493 {
494 SHIM_ERR("Unable to obtain default AppPatch directory\n");
495 return FALSE;
496 }
497 }
498
499 if (!db)
500 {
501 return SUCCEEDED(StringCchCopyW(path, size, default_dir));
502 }
503 else
504 {
505 SHIM_ERR("Unimplemented for db != NULL\n");
506 return FALSE;
507 }
508 }
509
510
511 /**
512 * Translates the given trWhich to a specific database / tagid
513 *
514 * @param [in] hsdb Handle to the database.
515 * @param [in] trWhich Tagref to find
516 * @param [out,opt] ppdb The Shim database that trWhich belongs to.
517 * @param [out,opt] ptiWhich The tagid that trWhich corresponds to.
518 *
519 * @return TRUE if it succeeds, FALSE if it fails.
520 */
521 BOOL WINAPI SdbTagRefToTagID(HSDB hsdb, TAGREF trWhich, PDB* ppdb, TAGID* ptiWhich)
522 {
523 if (trWhich & 0xf0000000)
524 {
525 SHIM_ERR("Multiple shim databases not yet implemented!\n");
526 if (ppdb)
527 *ppdb = NULL;
528 if (ptiWhich)
529 *ptiWhich = TAG_NULL;
530 return FALSE;
531 }
532
533 /* There seems to be no range checking on trWhich.. */
534 if (ppdb)
535 *ppdb = hsdb->db;
536 if (ptiWhich)
537 *ptiWhich = trWhich & 0x0fffffff;
538
539 return TRUE;
540 }
541
542 /**
543 * Translates the given trWhich to a specific database / tagid
544 *
545 * @param [in] hsdb Handle to the database.
546 * @param [in] pdb The Shim database that tiWhich belongs to.
547 * @param [in] tiWhich Path to executable for which we query database.
548 * @param [out,opt] ptrWhich The tagid that tiWhich corresponds to.
549 *
550 * @return TRUE if it succeeds, FALSE if it fails.
551 */
552 BOOL WINAPI SdbTagIDToTagRef(HSDB hsdb, PDB pdb, TAGID tiWhich, TAGREF* ptrWhich)
553 {
554 if (pdb != hsdb->db)
555 {
556 SHIM_ERR("Multiple shim databases not yet implemented!\n");
557 if (ptrWhich)
558 *ptrWhich = TAGREF_NULL;
559 return FALSE;
560 }
561
562 if (ptrWhich)
563 *ptrWhich = tiWhich & 0x0fffffff;
564
565 return TRUE;
566 }
567
568
569
570 BOOL WINAPI SdbPackAppCompatData(HSDB hsdb, PSDBQUERYRESULT pQueryResult, PVOID* ppData, DWORD *pdwSize)
571 {
572 ShimData* pData;
573 HRESULT hr;
574 DWORD n;
575
576 if (!pQueryResult || !ppData || !pdwSize)
577 {
578 SHIM_WARN("Invalid params: %p, %p, %p\n", pQueryResult, ppData, pdwSize);
579 return FALSE;
580 }
581
582 pData = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ShimData));
583 if (!pData)
584 {
585 SHIM_WARN("Unable to allocate %d bytes\n", sizeof(ShimData));
586 return FALSE;
587 }
588
589 GetWindowsDirectoryW(pData->szModule, _countof(pData->szModule));
590 hr = StringCchCatW(pData->szModule, _countof(pData->szModule), L"\\system32\\apphelp.dll");
591 if (!SUCCEEDED(hr))
592 {
593 SHIM_ERR("Unable to append module name (0x%x)\n", hr);
594 RtlFreeHeap(RtlGetProcessHeap(), 0, pData);
595 return FALSE;
596 }
597
598 pData->dwSize = sizeof(*pData);
599 pData->dwMagic = SHIMDATA_MAGIC;
600 pData->Query = *pQueryResult;
601 pData->unknown = 0;
602 pData->szLayer[0] = UNICODE_NULL; /* TODO */
603
604 SHIM_INFO("\ndwFlags 0x%x\ndwMagic 0x%x\ntrExe 0x%x\ntrLayer 0x%x",
605 pData->Query.dwFlags, pData->dwMagic, pData->Query.atrExes[0], pData->Query.atrLayers[0]);
606
607 /* Database List */
608 /* 0x0 {GUID} NAME */
609
610 for (n = 0; n < pQueryResult->dwLayerCount; ++n)
611 {
612 SHIM_INFO("Layer 0x%x\n", pQueryResult->atrLayers[n]);
613 }
614
615 *ppData = pData;
616 *pdwSize = pData->dwSize;
617
618 return TRUE;
619 }
620
621 BOOL WINAPI SdbUnpackAppCompatData(HSDB hsdb, LPCWSTR pszImageName, PVOID pData, PSDBQUERYRESULT pQueryResult)
622 {
623 ShimData* pShimData = pData;
624
625 if (!pShimData || pShimData->dwMagic != SHIMDATA_MAGIC || pShimData->dwSize < sizeof(ShimData))
626 return FALSE;
627
628 if (!pQueryResult)
629 return FALSE;
630
631 /* szLayer? */
632
633 *pQueryResult = pShimData->Query;
634 return TRUE;
635 }
636
637 DWORD WINAPI SdbGetAppCompatDataSize(ShimData* pData)
638 {
639 if (!pData || pData->dwMagic != SHIMDATA_MAGIC)
640 return 0;
641
642
643 return pData->dwSize;
644 }
645