xref: /reactos/dll/appcompat/apphelp/hsdb.c (revision 234f89c0)
1 /*
2  * PROJECT:     ReactOS Application compatibility module
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Shim matching / data (un)packing
5  * COPYRIGHT:   Copyright 2011 André Hentschel
6  *              Copyright 2013 Mislav Blaževic
7  *              Copyright 2015-2018 Mark Jansen (mark.jansen@reactos.org)
8  */
9 
10 #define WIN32_NO_STATUS
11 #include "windows.h"
12 #include "ntndk.h"
13 #include "strsafe.h"
14 #include "apphelp.h"
15 #include "compat_undoc.h"
16 
17 #define MAX_LAYER_LENGTH            256
18 #define GPLK_USER                   1
19 #define GPLK_MACHINE                2
20 
21 typedef struct _ShimData
22 {
23     WCHAR szModule[MAX_PATH];
24     DWORD dwSize;
25     DWORD dwMagic;
26     SDBQUERYRESULT Query;
27     WCHAR szLayer[MAX_LAYER_LENGTH];
28     DWORD dwRosProcessCompatVersion;  // ReactOS specific
29 } ShimData;
30 
31 #define SHIMDATA_MAGIC  0xAC0DEDAB
32 
33 
34 C_ASSERT(SHIMDATA_MAGIC == REACTOS_SHIMDATA_MAGIC);
35 C_ASSERT(sizeof(ShimData) == sizeof(ReactOS_ShimData));
36 C_ASSERT(offsetof(ShimData, dwMagic) == offsetof(ReactOS_ShimData, dwMagic));
37 C_ASSERT(offsetof(ShimData, dwRosProcessCompatVersion) == offsetof(ReactOS_ShimData, dwRosProcessCompatVersion));
38 
39 
40 static BOOL WINAPI SdbpFileExists(LPCWSTR path)
41 {
42     DWORD attr = GetFileAttributesW(path);
43     return (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY));
44 }
45 
46 /* Given a 'MATCHING_FILE' tag and an ATTRINFO array,
47    check all tags defined in the MATCHING_FILE against the ATTRINFO */
48 static BOOL SdbpMatchFileAttributes(PDB pdb, TAGID matching_file, PATTRINFO attribs, DWORD attr_count)
49 {
50     TAGID child;
51 
52     for (child = SdbGetFirstChild(pdb, matching_file);
53          child != TAGID_NULL; child = SdbGetNextChild(pdb, matching_file, child))
54     {
55         TAG tag = SdbGetTagFromTagID(pdb, child);
56         DWORD n;
57 
58         /* Already handled! */
59         if (tag == TAG_NAME)
60             continue;
61 
62         if (tag == TAG_UPTO_BIN_FILE_VERSION ||
63             tag == TAG_UPTO_BIN_PRODUCT_VERSION ||
64             tag == TAG_UPTO_LINK_DATE)
65         {
66             SHIM_WARN("Unimplemented TAG_UPTO_XXXXX\n");
67             continue;
68         }
69 
70         for (n = 0; n < attr_count; ++n)
71         {
72             PATTRINFO attr = attribs + n;
73             if (attr->flags == ATTRIBUTE_AVAILABLE && attr->type == tag)
74             {
75                 DWORD dwval;
76                 WCHAR* lpval;
77                 QWORD qwval;
78                 switch (tag & TAG_TYPE_MASK)
79                 {
80                 case TAG_TYPE_DWORD:
81                     dwval = SdbReadDWORDTag(pdb, child, 0);
82                     if (dwval != attr->dwattr)
83                         return FALSE;
84                     break;
85                 case TAG_TYPE_STRINGREF:
86                     lpval = SdbGetStringTagPtr(pdb, child);
87                     if (!lpval || wcsicmp(attr->lpattr, lpval))
88                         return FALSE;
89                     break;
90                 case TAG_TYPE_QWORD:
91                     qwval = SdbReadQWORDTag(pdb, child, 0);
92                     if (qwval != attr->qwattr)
93                         return FALSE;
94                     break;
95                 default:
96                     SHIM_WARN("Unhandled type 0x%x MATCHING_FILE\n", (tag & TAG_TYPE_MASK));
97                     return FALSE;
98                 }
99             }
100         }
101         if (n == attr_count)
102             SHIM_WARN("Unhandled tag %ws in MACHING_FILE\n", SdbTagToString(tag));
103     }
104     return TRUE;
105 }
106 
107 /* Given an 'exe' tag and an ATTRINFO array (for the main file),
108    verify that the main file and any additional files match */
109 static BOOL WINAPI SdbpMatchExe(PDB pdb, TAGID exe, const WCHAR* dir, PATTRINFO main_attribs, DWORD main_attr_count)
110 {
111     RTL_UNICODE_STRING_BUFFER FullPathName = { { 0 } };
112     WCHAR FullPathBuffer[MAX_PATH];
113     UNICODE_STRING UnicodeDir;
114     TAGID matching_file;
115     PATTRINFO attribs = NULL;
116     DWORD attr_count;
117     BOOL IsMatch = FALSE;
118 
119     RtlInitUnicodeString(&UnicodeDir, dir);
120     RtlInitBuffer(&FullPathName.ByteBuffer, (PUCHAR)FullPathBuffer, sizeof(FullPathBuffer));
121 
122     for (matching_file = SdbFindFirstTag(pdb, exe, TAG_MATCHING_FILE);
123             matching_file != TAGID_NULL; matching_file = SdbFindNextTag(pdb, exe, matching_file))
124     {
125         TAGID tagName = SdbFindFirstTag(pdb, matching_file, TAG_NAME);
126         UNICODE_STRING Name;
127         USHORT Len;
128 
129         RtlInitUnicodeString(&Name, SdbGetStringTagPtr(pdb, tagName));
130 
131         if (!Name.Buffer)
132             goto Cleanup;
133 
134         /* An '*' here means use the main executable' */
135         if (!wcscmp(Name.Buffer, L"*"))
136         {
137             /* We already have these attributes, so we do not need to retrieve them */
138             if (!SdbpMatchFileAttributes(pdb, matching_file, main_attribs, main_attr_count))
139                 goto Cleanup;
140             continue;
141         }
142 
143         /* Technically, one UNICODE_NULL and one path separator. */
144         Len = UnicodeDir.Length + Name.Length + sizeof(UNICODE_NULL) + sizeof(UNICODE_NULL);
145         if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY, &FullPathName.ByteBuffer, Len)))
146             goto Cleanup;
147 
148         if (Len > FullPathName.ByteBuffer.Size)
149             goto Cleanup;
150 
151         RtlInitEmptyUnicodeString(&FullPathName.String, (PWCHAR)FullPathName.ByteBuffer.Buffer, FullPathName.ByteBuffer.Size);
152 
153         RtlCopyUnicodeString(&FullPathName.String, &UnicodeDir);
154         RtlAppendUnicodeToString(&FullPathName.String, L"\\");
155         RtlAppendUnicodeStringToString(&FullPathName.String, &Name);
156 
157         /* If the file does not exist, do not bother trying to read it's attributes */
158         if (!SdbpFileExists(FullPathName.String.Buffer))
159             goto Cleanup;
160 
161         /* Do we have some attributes from the previous iteration? */
162         if (attribs)
163             SdbFreeFileAttributes(attribs);
164 
165         if (!SdbGetFileAttributes(FullPathName.String.Buffer, &attribs, &attr_count))
166             goto Cleanup;
167 
168         if (!SdbpMatchFileAttributes(pdb, matching_file, attribs, attr_count))
169             goto Cleanup;
170     }
171 
172     IsMatch = TRUE;
173 
174 Cleanup:
175     RtlFreeBuffer(&FullPathName.ByteBuffer);
176     if (attribs)
177         SdbFreeFileAttributes(attribs);
178 
179     return IsMatch;
180 }
181 
182 /* Add a database guid to the query result */
183 static void SdbpAddDatabaseGuid(PDB pdb, PSDBQUERYRESULT result)
184 {
185     size_t n;
186 
187     for (n = 0; n < _countof(result->rgGuidDB); ++n)
188     {
189         if (!memcmp(&result->rgGuidDB[n], &pdb->database_id, sizeof(pdb->database_id)))
190             return;
191 
192         if (result->dwCustomSDBMap & (1<<n))
193             continue;
194 
195         memcpy(&result->rgGuidDB[n], &pdb->database_id, sizeof(result->rgGuidDB[n]));
196         result->dwCustomSDBMap |= (1<<n);
197         return;
198     }
199 }
200 
201 /* Add one layer to the query result */
202 static BOOL SdbpAddSingleLayerMatch(TAGREF layer, PSDBQUERYRESULT result)
203 {
204     size_t n;
205 
206     for (n = 0; n < result->dwLayerCount; ++n)
207     {
208         if (result->atrLayers[n] == layer)
209             return FALSE;
210     }
211 
212     if (n >= _countof(result->atrLayers))
213         return FALSE;
214 
215     result->atrLayers[n] = layer;
216     result->dwLayerCount++;
217 
218     return TRUE;
219 }
220 
221 /* Translate a layer name to a tagref + add it to the query result */
222 static BOOL SdbpAddNamedLayerMatch(HSDB hsdb, PCWSTR layerName, PSDBQUERYRESULT result)
223 {
224     TAGID database, layer;
225     TAGREF tr;
226     PDB pdb = hsdb->pdb;
227 
228     database = SdbFindFirstTag(pdb, TAGID_ROOT, TAG_DATABASE);
229     if (database == TAGID_NULL)
230         return FALSE;
231 
232     layer = SdbFindFirstNamedTag(pdb, database, TAG_LAYER, TAG_NAME, layerName);
233     if (layer == TAGID_NULL)
234         return FALSE;
235 
236     if (!SdbTagIDToTagRef(hsdb, pdb, layer, &tr))
237         return FALSE;
238 
239     if (!SdbpAddSingleLayerMatch(tr, result))
240         return FALSE;
241 
242     SdbpAddDatabaseGuid(pdb, result);
243     return TRUE;
244 }
245 
246 /* Add all layers for the exe tag to the query result */
247 static void SdbpAddExeLayers(HSDB hsdb, PDB pdb, TAGID tagExe, PSDBQUERYRESULT result)
248 {
249     TAGID layer = SdbFindFirstTag(pdb, tagExe, TAG_LAYER);
250 
251     while (layer != TAGID_NULL)
252     {
253         TAGREF tr;
254         TAGID layerIdTag = SdbFindFirstTag(pdb, layer, TAG_LAYER_TAGID);
255         DWORD tagId = SdbReadDWORDTag(pdb, layerIdTag, TAGID_NULL);
256 
257         if (layerIdTag != TAGID_NULL &&
258             tagId != TAGID_NULL &&
259             SdbTagIDToTagRef(hsdb, pdb, tagId, &tr))
260         {
261             SdbpAddSingleLayerMatch(tr, result);
262         }
263         else
264         {
265             /* Try a name lookup */
266             TAGID layerTag = SdbFindFirstTag(pdb, layer, TAG_NAME);
267             if (layerTag != TAGID_NULL)
268             {
269                 LPCWSTR layerName = SdbGetStringTagPtr(pdb, layerTag);
270                 if (layerName)
271                 {
272                     SdbpAddNamedLayerMatch(hsdb, layerName, result);
273                 }
274             }
275         }
276 
277         layer = SdbFindNextTag(pdb, tagExe, layer);
278     }
279 }
280 
281 /* Add an exe tag to the query result */
282 static void SdbpAddExeMatch(HSDB hsdb, PDB pdb, TAGID tagExe, PSDBQUERYRESULT result)
283 {
284     size_t n;
285     TAGREF tr;
286 
287     if (!SdbTagIDToTagRef(hsdb, pdb, tagExe, &tr))
288         return;
289 
290     for (n = 0; n < result->dwExeCount; ++n)
291     {
292         if (result->atrExes[n] == tr)
293             return;
294     }
295 
296     if (n >= _countof(result->atrExes))
297         return;
298 
299     result->atrExes[n] = tr;
300     result->dwExeCount++;
301 
302     SdbpAddExeLayers(hsdb, pdb, tagExe, result);
303 
304     SdbpAddDatabaseGuid(pdb, result);
305 }
306 
307 /* Add all named layers to the query result */
308 static ULONG SdbpAddLayerMatches(HSDB hsdb, PWSTR pwszLayers, DWORD pdwBytes, PSDBQUERYRESULT result)
309 {
310     PWSTR start = pwszLayers, p;
311     ULONG Added = 0;
312 
313     const PWSTR end = pwszLayers + (pdwBytes / sizeof(WCHAR));
314     while (start < end && (*start == L'!' || *start == L'#' || *start == L' ' || *start == L'\t'))
315         start++;
316 
317     if (start == end)
318         return 0;
319 
320     do
321     {
322         while (*start == L' ' || *start == L'\t')
323             ++start;
324 
325         if (*start == UNICODE_NULL)
326             break;
327         p = wcspbrk(start, L" \t");
328 
329         if (p)
330             *p = UNICODE_NULL;
331 
332         if (SdbpAddNamedLayerMatch(hsdb, start, result))
333             Added++;
334 
335         start = p + 1;
336     } while (start < end && p);
337 
338     return Added;
339 }
340 
341 static BOOL SdbpPropagateEnvLayers(HSDB hsdb, LPWSTR Environment, PSDBQUERYRESULT Result)
342 {
343     static const UNICODE_STRING EnvKey = RTL_CONSTANT_STRING(L"__COMPAT_LAYER");
344     UNICODE_STRING EnvValue;
345     NTSTATUS Status;
346     WCHAR Buffer[MAX_LAYER_LENGTH];
347 
348     RtlInitEmptyUnicodeString(&EnvValue, Buffer, sizeof(Buffer));
349 
350     Status = RtlQueryEnvironmentVariable_U(Environment, &EnvKey, &EnvValue);
351 
352     if (!NT_SUCCESS(Status))
353         return FALSE;
354 
355     return SdbpAddLayerMatches(hsdb, Buffer, EnvValue.Length, Result) > 0;
356 }
357 
358 
359 
360 /**
361  * Opens specified shim database file. Handle returned by this function may only be used by
362  * functions which take HSDB param thus differing it from SdbOpenDatabase.
363  *
364  * @param [in]  flags   Specifies type of path or predefined database.
365  * @param [in]  path    Path to the shim database file.
366  *
367  * @return  Success: Handle to the opened shim database, NULL otherwise.
368  */
369 HSDB WINAPI SdbInitDatabase(DWORD flags, LPCWSTR path)
370 {
371     static const WCHAR shim[] = {'\\','s','y','s','m','a','i','n','.','s','d','b',0};
372     static const WCHAR msi[] = {'\\','m','s','i','m','a','i','n','.','s','d','b',0};
373     static const WCHAR drivers[] = {'\\','d','r','v','m','a','i','n','.','s','d','b',0};
374     LPCWSTR name;
375     WCHAR buffer[128];
376     HSDB hsdb;
377 
378     hsdb = SdbAlloc(sizeof(SDB));
379     if (!hsdb)
380         return NULL;
381     hsdb->auto_loaded = 0;
382 
383     /* Check for predefined databases */
384     if ((flags & HID_DATABASE_TYPE_MASK) && path == NULL)
385     {
386         switch (flags & HID_DATABASE_TYPE_MASK)
387         {
388             case SDB_DATABASE_MAIN_SHIM: name = shim; break;
389             case SDB_DATABASE_MAIN_MSI: name = msi; break;
390             case SDB_DATABASE_MAIN_DRIVERS: name = drivers; break;
391             default:
392                 SdbReleaseDatabase(hsdb);
393                 return NULL;
394         }
395         SdbGetAppPatchDir(NULL, buffer, _countof(buffer));
396         StringCchCatW(buffer, _countof(buffer), name);
397         flags = HID_DOS_PATHS;
398     }
399 
400     hsdb->pdb = SdbOpenDatabase(path ? path : buffer, (flags & 0xF) - 1);
401 
402     /* If database could not be loaded, a handle doesn't make sense either */
403     if (!hsdb->pdb)
404     {
405         SdbReleaseDatabase(hsdb);
406         return NULL;
407     }
408 
409     return hsdb;
410 }
411 
412 /**
413  * Closes shim database opened by SdbInitDatabase.
414  *
415  * @param [in]  hsdb    Handle to the shim database.
416  */
417 void WINAPI SdbReleaseDatabase(HSDB hsdb)
418 {
419     SdbCloseDatabase(hsdb->pdb);
420     SdbFree(hsdb);
421 }
422 
423 /**
424  * Queries database for a specified exe If hsdb is NULL default database shall be loaded and
425  * searched.
426  *
427  * @param [in]  hsdb        Handle to the shim database.
428  * @param [in]  path        Path to executable for which we query database.
429  * @param [in]  module_name Unused.
430  * @param [in]  env         The environment block to use
431  * @param [in]  flags       0 or SDBGMEF_IGNORE_ENVIRONMENT.
432  * @param [out] result      Pointer to structure in which query result shall be stored.
433  *
434  * @return  TRUE if it succeeds, FALSE if it fails.
435  */
436 BOOL WINAPI SdbGetMatchingExe(HSDB hsdb, LPCWSTR path, LPCWSTR module_name,
437                               LPCWSTR env, DWORD flags, PSDBQUERYRESULT result)
438 {
439     BOOL ret = FALSE;
440     TAGID database, iter, name;
441     PATTRINFO attribs = NULL;
442     DWORD attr_count;
443     RTL_UNICODE_STRING_BUFFER DosApplicationName = { { 0 } };
444     WCHAR DosPathBuffer[MAX_PATH];
445     ULONG PathType = 0;
446     LPWSTR file_name;
447     WCHAR wszLayers[MAX_LAYER_LENGTH];
448     DWORD dwSize;
449     PDB pdb;
450 
451     /* Load default database if one is not specified */
452     if (!hsdb)
453     {
454         /* To reproduce windows behaviour HID_DOS_PATHS needs
455          * to be specified when loading default database */
456         hsdb = SdbInitDatabase(HID_DOS_PATHS | SDB_DATABASE_MAIN_SHIM, NULL);
457         if (hsdb)
458             hsdb->auto_loaded = TRUE;
459     }
460 
461     ZeroMemory(result, sizeof(*result));
462 
463     /* No database could be loaded */
464     if (!hsdb || !path)
465         return FALSE;
466 
467     /* We do not support multiple db's yet! */
468     pdb = hsdb->pdb;
469 
470     RtlInitUnicodeString(&DosApplicationName.String, path);
471     RtlInitBuffer(&DosApplicationName.ByteBuffer, (PUCHAR)DosPathBuffer, sizeof(DosPathBuffer));
472     if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY, &DosApplicationName.ByteBuffer, DosApplicationName.String.MaximumLength)))
473     {
474         SHIM_ERR("Failed to convert allocate buffer.");
475         goto Cleanup;
476     }
477     /* Update the internal buffer to contain the string */
478     memcpy(DosApplicationName.ByteBuffer.Buffer, path, DosApplicationName.String.MaximumLength);
479     /* Make sure the string uses our internal buffer (we want to modify the buffer,
480         and RtlNtPathNameToDosPathName does not always modify the String to point to the Buffer)! */
481     DosApplicationName.String.Buffer = (PWSTR)DosApplicationName.ByteBuffer.Buffer;
482 
483     if (!NT_SUCCESS(RtlNtPathNameToDosPathName(0, &DosApplicationName, &PathType, NULL)))
484     {
485         SHIM_ERR("Failed to convert %S to DOS Path.", path);
486         goto Cleanup;
487     }
488 
489 
490     /* Extract file name */
491     file_name = wcsrchr(DosApplicationName.String.Buffer, '\\');
492     if (!file_name)
493     {
494         SHIM_ERR("Failed to find Exe name in %wZ.", &DosApplicationName.String);
495         goto Cleanup;
496     }
497 
498     /* We will use the buffer for exe name and directory. */
499     *(file_name++) = UNICODE_NULL;
500 
501     /* DATABASE is list TAG which contains all executables */
502     database = SdbFindFirstTag(pdb, TAGID_ROOT, TAG_DATABASE);
503     if (database == TAGID_NULL)
504     {
505         goto Cleanup;
506     }
507 
508     /* EXE is list TAG which contains data required to match executable */
509     iter = SdbFindFirstTag(pdb, database, TAG_EXE);
510 
511     /* Search for entry in database, we should look into indexing tags! */
512     while (iter != TAGID_NULL)
513     {
514         LPWSTR foundName;
515         /* Check if exe name matches */
516         name = SdbFindFirstTag(pdb, iter, TAG_NAME);
517         /* If this is a malformed DB, (no TAG_NAME), we should not crash. */
518         foundName = SdbGetStringTagPtr(pdb, name);
519         if (foundName && !wcsicmp(foundName, file_name))
520         {
521             /* Get information about executable required to match it with database entry */
522             if (!attribs)
523             {
524                 if (!SdbGetFileAttributes(path, &attribs, &attr_count))
525                     goto Cleanup;
526             }
527 
528 
529             /* We have a null terminator before the application name, so DosApplicationName only contains the path. */
530             if (SdbpMatchExe(pdb, iter, DosApplicationName.String.Buffer, attribs, attr_count))
531             {
532                 ret = TRUE;
533                 SdbpAddExeMatch(hsdb, pdb, iter, result);
534             }
535         }
536 
537         /* Continue iterating */
538         iter = SdbFindNextTag(pdb, database, iter);
539     }
540 
541     /* Restore the full path. */
542     *(--file_name) = L'\\';
543 
544     dwSize = sizeof(wszLayers);
545     if (SdbGetPermLayerKeys(DosApplicationName.String.Buffer, wszLayers, &dwSize, GPLK_MACHINE | GPLK_USER))
546     {
547         SdbpAddLayerMatches(hsdb, wszLayers, dwSize, result);
548         ret = TRUE;
549     }
550 
551     if (!(flags & SDBGMEF_IGNORE_ENVIRONMENT))
552     {
553         if (SdbpPropagateEnvLayers(hsdb, (LPWSTR)env, result))
554         {
555             ret = TRUE;
556             result->dwFlags |= SHIMREG_HAS_ENVIRONMENT;
557         }
558     }
559 
560 Cleanup:
561     RtlFreeBuffer(&DosApplicationName.ByteBuffer);
562     if (attribs)
563         SdbFreeFileAttributes(attribs);
564     if (hsdb->auto_loaded)
565         SdbReleaseDatabase(hsdb);
566     return ret;
567 }
568 
569 /**
570  * Retrieves AppPatch directory.
571  *
572  * @param [in]  pdb     Handle to the shim database.
573  * @param [out] path    Pointer to memory in which path shall be written.
574  * @param [in]  size    Size of the buffer in characters.
575  */
576 HRESULT WINAPI SdbGetAppPatchDir(HSDB hsdb, LPWSTR path, DWORD size)
577 {
578     static WCHAR* default_dir = NULL;
579     static CONST WCHAR szAppPatch[] = {'\\','A','p','p','P','a','t','c','h',0};
580 
581     /* In case function fails, path holds empty string */
582     if (size > 0)
583         *path = 0;
584 
585     if (!default_dir)
586     {
587         WCHAR* tmp;
588         HRESULT hr = E_FAIL;
589         UINT len = GetSystemWindowsDirectoryW(NULL, 0) + SdbpStrlen(szAppPatch);
590         tmp = SdbAlloc((len + 1)* sizeof(WCHAR));
591         if (tmp)
592         {
593             UINT r = GetSystemWindowsDirectoryW(tmp, len+1);
594             if (r && r < len)
595             {
596                 hr = StringCchCatW(tmp, len+1, szAppPatch);
597                 if (SUCCEEDED(hr))
598                 {
599                     if (InterlockedCompareExchangePointer((void**)&default_dir, tmp, NULL) == NULL)
600                         tmp = NULL;
601                 }
602             }
603             if (tmp)
604                 SdbFree(tmp);
605         }
606         if (!default_dir)
607         {
608             SHIM_ERR("Unable to obtain default AppPatch directory (0x%x)\n", hr);
609             return hr;
610         }
611     }
612 
613     if (!hsdb)
614     {
615         return StringCchCopyW(path, size, default_dir);
616     }
617     else
618     {
619         SHIM_ERR("Unimplemented for hsdb != NULL\n");
620         return E_NOTIMPL;
621     }
622 }
623 
624 
625 /**
626  * Translates the given trWhich to a specific database / tagid
627  *
628  * @param [in]      hsdb        Handle to the database.
629  * @param [in]      trWhich     Tagref to find
630  * @param [out,opt] ppdb        The Shim database that trWhich belongs to.
631  * @param [out,opt] ptiWhich    The tagid that trWhich corresponds to.
632  *
633  * @return  TRUE if it succeeds, FALSE if it fails.
634  */
635 BOOL WINAPI SdbTagRefToTagID(HSDB hsdb, TAGREF trWhich, PDB* ppdb, TAGID* ptiWhich)
636 {
637     if (trWhich & 0xf0000000)
638     {
639         SHIM_ERR("Multiple shim databases not yet implemented!\n");
640         if (ppdb)
641             *ppdb = NULL;
642         if (ptiWhich)
643             *ptiWhich = TAG_NULL;
644         return FALSE;
645     }
646 
647     /* There seems to be no range checking on trWhich.. */
648     if (ppdb)
649         *ppdb = hsdb->pdb;
650     if (ptiWhich)
651         *ptiWhich = trWhich & 0x0fffffff;
652 
653     return TRUE;
654 }
655 
656 /**
657  * Translates the given trWhich to a specific database / tagid
658  *
659  * @param [in]      hsdb        Handle to the database.
660  * @param [in]      pdb         The Shim database that tiWhich belongs to.
661  * @param [in]      tiWhich     Path to executable for which we query database.
662  * @param [out,opt] ptrWhich    The tagid that tiWhich corresponds to.
663  *
664  * @return  TRUE if it succeeds, FALSE if it fails.
665  */
666 BOOL WINAPI SdbTagIDToTagRef(HSDB hsdb, PDB pdb, TAGID tiWhich, TAGREF* ptrWhich)
667 {
668     if (pdb != hsdb->pdb)
669     {
670         SHIM_ERR("Multiple shim databases not yet implemented!\n");
671         if (ptrWhich)
672             *ptrWhich = TAGREF_NULL;
673         return FALSE;
674     }
675 
676     if (ptrWhich)
677         *ptrWhich = tiWhich & 0x0fffffff;
678 
679     return TRUE;
680 }
681 
682 
683 /* Convert a query result to shim data that will be loaded in the child process */
684 BOOL WINAPI SdbPackAppCompatData(HSDB hsdb, PSDBQUERYRESULT pQueryResult, PVOID* ppData, DWORD *pdwSize)
685 {
686     ShimData* pData;
687     HRESULT hr;
688     DWORD n;
689     BOOL bCloseDatabase = FALSE;
690 
691     if (!pQueryResult || !ppData || !pdwSize)
692     {
693         SHIM_WARN("Invalid params: %p, %p, %p\n", pQueryResult, ppData, pdwSize);
694         return FALSE;
695     }
696 
697     pData = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ShimData));
698     if (!pData)
699     {
700         SHIM_WARN("Unable to allocate %d bytes\n", sizeof(ShimData));
701         return FALSE;
702     }
703 
704     GetSystemWindowsDirectoryW(pData->szModule, _countof(pData->szModule));
705     hr = StringCchCatW(pData->szModule, _countof(pData->szModule), L"\\system32\\apphelp.dll");
706     if (!SUCCEEDED(hr))
707     {
708         SHIM_ERR("Unable to append module name (0x%x)\n", hr);
709         RtlFreeHeap(RtlGetProcessHeap(), 0, pData);
710         return FALSE;
711     }
712 
713     pData->dwSize = sizeof(*pData);
714     pData->dwMagic = SHIMDATA_MAGIC;
715     pData->Query = *pQueryResult;
716     pData->dwRosProcessCompatVersion = 0;
717     pData->szLayer[0] = UNICODE_NULL;   /* TODO */
718 
719     SHIM_INFO("\ndwFlags    0x%x\ndwMagic    0x%x\ntrExe      0x%x\ntrLayer    0x%x\n",
720               pData->Query.dwFlags, pData->dwMagic, pData->Query.atrExes[0], pData->Query.atrLayers[0]);
721 
722     /* Database List */
723     /* 0x0 {GUID} NAME: Use to open HSDB */
724     if (hsdb == NULL)
725     {
726         hsdb = SdbInitDatabase(HID_DOS_PATHS | SDB_DATABASE_MAIN_SHIM, NULL);
727         bCloseDatabase = TRUE;
728     }
729 
730     for (n = 0; n < pQueryResult->dwLayerCount; ++n)
731     {
732         DWORD dwValue = 0, dwType;
733         DWORD dwValueSize = sizeof(dwValue);
734         SHIM_INFO("Layer 0x%x\n", pQueryResult->atrLayers[n]);
735 
736         if (SdbQueryData(hsdb, pQueryResult->atrLayers[n], L"SHIMVERSIONNT", &dwType, &dwValue, &dwValueSize) == ERROR_SUCCESS &&
737             dwType == REG_DWORD && dwValueSize == sizeof(dwValue))
738         {
739             dwValue = (dwValue % 100) | ((dwValue / 100) << 8);
740             if (dwValue > pData->dwRosProcessCompatVersion)
741                 pData->dwRosProcessCompatVersion = dwValue;
742         }
743     }
744 
745     if (pData->dwRosProcessCompatVersion)
746         SHIM_INFO("Setting ProcessCompatVersion 0x%x\n", pData->dwRosProcessCompatVersion);
747 
748     if (bCloseDatabase)
749         SdbReleaseDatabase(hsdb);
750 
751     *ppData = pData;
752     *pdwSize = pData->dwSize;
753 
754     return TRUE;
755 }
756 
757 BOOL WINAPI SdbUnpackAppCompatData(HSDB hsdb, LPCWSTR pszImageName, PVOID pData, PSDBQUERYRESULT pQueryResult)
758 {
759     ShimData* pShimData = pData;
760 
761     if (!pShimData || pShimData->dwMagic != SHIMDATA_MAGIC || pShimData->dwSize < sizeof(ShimData))
762         return FALSE;
763 
764     if (!pQueryResult)
765         return FALSE;
766 
767     /* szLayer? */
768 
769     *pQueryResult = pShimData->Query;
770     return TRUE;
771 }
772 
773 DWORD WINAPI SdbGetAppCompatDataSize(ShimData* pData)
774 {
775     if (!pData || pData->dwMagic != SHIMDATA_MAGIC)
776         return 0;
777 
778     return pData->dwSize;
779 }
780 
781 
782 /**
783 * Retrieve a Data entry
784 *
785 * @param [in]  hsdb                    The multi-database.
786 * @param [in]  trExe                   The tagRef to start at
787 * @param [in,opt]  lpszDataName        The name of the Data entry to find, or NULL to return all.
788 * @param [out,opt]  lpdwDataType       Any of REG_SZ, REG_QWORD, REG_DWORD, ...
789 * @param [out]  lpBuffer               The output buffer
790 * @param [in,out,opt]  lpcbBufferSize  The size of lpBuffer in bytes
791 * @param [out,opt]  ptrData            The tagRef of the data
792 *
793 * @return  ERROR_SUCCESS
794 */
795 DWORD WINAPI SdbQueryDataEx(HSDB hsdb, TAGREF trWhich, LPCWSTR lpszDataName, LPDWORD lpdwDataType, LPVOID lpBuffer, LPDWORD lpcbBufferSize, TAGREF *ptrData)
796 {
797     PDB pdb;
798     TAGID tiWhich, tiData;
799     DWORD dwResult;
800 
801     if (!SdbTagRefToTagID(hsdb, trWhich, &pdb, &tiWhich))
802     {
803         SHIM_WARN("Unable to translate trWhich=0x%x\n", trWhich);
804         return ERROR_NOT_FOUND;
805     }
806 
807     dwResult = SdbQueryDataExTagID(pdb, tiWhich, lpszDataName, lpdwDataType, lpBuffer, lpcbBufferSize, &tiData);
808 
809     if (dwResult == ERROR_SUCCESS && ptrData)
810         SdbTagIDToTagRef(hsdb, pdb, tiData, ptrData);
811 
812     return dwResult;
813 }
814 
815 
816 /**
817 * Retrieve a Data entry
818 *
819 * @param [in]  hsdb                    The multi-database.
820 * @param [in]  trExe                   The tagRef to start at
821 * @param [in,opt]  lpszDataName        The name of the Data entry to find, or NULL to return all.
822 * @param [out,opt]  lpdwDataType       Any of REG_SZ, REG_QWORD, REG_DWORD, ...
823 * @param [out]  lpBuffer               The output buffer
824 * @param [in,out,opt]  lpcbBufferSize  The size of lpBuffer in bytes
825 *
826 * @return  ERROR_SUCCESS
827 */
828 DWORD WINAPI SdbQueryData(HSDB hsdb, TAGREF trWhich, LPCWSTR lpszDataName, LPDWORD lpdwDataType, LPVOID lpBuffer, LPDWORD lpcbBufferSize)
829 {
830     return SdbQueryDataEx(hsdb, trWhich, lpszDataName, lpdwDataType, lpBuffer, lpcbBufferSize, NULL);
831 }
832