2 * Copyright 2011 André Hentschel
3 * Copyright 2013 Mislav Blažević
4 * Copyright 2015-2017 Mark Jansen
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.
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.
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
21 #define WIN32_NO_STATUS
27 #include "wine/unicode.h"
29 #define MAX_LAYER_LENGTH 256
31 #define GPLK_MACHINE 2
33 typedef struct _ShimData
35 WCHAR szModule
[MAX_PATH
];
39 WCHAR szLayer
[MAX_LAYER_LENGTH
];
40 DWORD unknown
; // 0x14c
43 #define SHIMDATA_MAGIC 0xAC0DEDAB
46 static BOOL WINAPI
SdbpFileExists(LPCWSTR path
)
48 DWORD attr
= GetFileAttributesW(path
);
49 return (attr
!= INVALID_FILE_ATTRIBUTES
&& !(attr
& FILE_ATTRIBUTE_DIRECTORY
));
52 static BOOL
SdbpMatchFileAttributes(PDB pdb
, TAGID matching_file
, PATTRINFO attribs
, DWORD attr_count
)
56 for (child
= SdbGetFirstChild(pdb
, matching_file
);
57 child
!= TAGID_NULL
; child
= SdbGetNextChild(pdb
, matching_file
, child
))
59 TAG tag
= SdbGetTagFromTagID(pdb
, child
);
62 /* Already handled! */
66 if (tag
== TAG_UPTO_BIN_FILE_VERSION
||
67 tag
== TAG_UPTO_BIN_PRODUCT_VERSION
||
68 tag
== TAG_UPTO_LINK_DATE
)
70 SHIM_WARN("Unimplemented TAG_UPTO_XXXXX\n");
74 for (n
= 0; n
< attr_count
; ++n
)
76 PATTRINFO attr
= attribs
+ n
;
77 if (attr
->flags
== ATTRIBUTE_AVAILABLE
&& attr
->type
== tag
)
82 switch (tag
& TAG_TYPE_MASK
)
85 dwval
= SdbReadDWORDTag(pdb
, child
, 0);
86 if (dwval
!= attr
->dwattr
)
89 case TAG_TYPE_STRINGREF
:
90 lpval
= SdbGetStringTagPtr(pdb
, child
);
91 if (!lpval
|| wcsicmp(attr
->lpattr
, lpval
))
95 qwval
= SdbReadQWORDTag(pdb
, child
, 0);
96 if (qwval
!= attr
->qwattr
)
100 SHIM_WARN("Unhandled type 0x%x MATCHING_FILE\n", (tag
& TAG_TYPE_MASK
));
106 SHIM_WARN("Unhandled tag %ws in MACHING_FILE\n", SdbTagToString(tag
));
111 static BOOL WINAPI
SdbpMatchExe(PDB pdb
, TAGID exe
, const WCHAR
* dir
, PATTRINFO main_attribs
, DWORD main_attr_count
)
113 RTL_UNICODE_STRING_BUFFER FullPathName
= { { 0 } };
114 WCHAR FullPathBuffer
[MAX_PATH
];
115 UNICODE_STRING UnicodeDir
;
117 PATTRINFO attribs
= NULL
;
119 BOOL IsMatch
= FALSE
;
121 RtlInitUnicodeString(&UnicodeDir
, dir
);
122 RtlInitBuffer(&FullPathName
.ByteBuffer
, (PUCHAR
)FullPathBuffer
, sizeof(FullPathBuffer
));
124 for (matching_file
= SdbFindFirstTag(pdb
, exe
, TAG_MATCHING_FILE
);
125 matching_file
!= TAGID_NULL
; matching_file
= SdbFindNextTag(pdb
, exe
, matching_file
))
127 TAGID tagName
= SdbFindFirstTag(pdb
, matching_file
, TAG_NAME
);
131 RtlInitUnicodeString(&Name
, SdbGetStringTagPtr(pdb
, tagName
));
136 if (!wcscmp(Name
.Buffer
, L
"*"))
138 if (!SdbpMatchFileAttributes(pdb
, matching_file
, main_attribs
, main_attr_count
))
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
)))
148 if (Len
> FullPathName
.ByteBuffer
.Size
)
151 RtlInitEmptyUnicodeString(&FullPathName
.String
, (PWCHAR
)FullPathName
.ByteBuffer
.Buffer
, FullPathName
.ByteBuffer
.Size
);
153 RtlCopyUnicodeString(&FullPathName
.String
, &UnicodeDir
);
154 RtlAppendUnicodeToString(&FullPathName
.String
, L
"\\");
155 RtlAppendUnicodeStringToString(&FullPathName
.String
, &Name
);
157 if (!SdbpFileExists(FullPathName
.String
.Buffer
))
161 SdbFreeFileAttributes(attribs
);
163 if (!SdbGetFileAttributes(FullPathName
.String
.Buffer
, &attribs
, &attr_count
))
166 if (!SdbpMatchFileAttributes(pdb
, matching_file
, attribs
, attr_count
))
173 RtlFreeBuffer(&FullPathName
.ByteBuffer
);
175 SdbFreeFileAttributes(attribs
);
180 static void SdbpAddDatabaseGuid(PDB db
, PSDBQUERYRESULT result
)
184 for (n
= 0; n
< _countof(result
->rgGuidDB
); ++n
)
186 if (!memcmp(&result
->rgGuidDB
[n
], &db
->database_id
, sizeof(db
->database_id
)))
189 if (result
->dwCustomSDBMap
& (1<<n
))
192 memcpy(&result
->rgGuidDB
[n
], &db
->database_id
, sizeof(result
->rgGuidDB
[n
]));
193 result
->dwCustomSDBMap
|= (1<<n
);
198 static BOOL
SdbpAddSingleLayerMatch(TAGREF layer
, PSDBQUERYRESULT result
)
202 for (n
= 0; n
< result
->dwLayerCount
; ++n
)
204 if (result
->atrLayers
[n
] == layer
)
208 if (n
>= _countof(result
->atrLayers
))
211 result
->atrLayers
[n
] = layer
;
212 result
->dwLayerCount
++;
218 static BOOL
SdbpAddNamedLayerMatch(HSDB hsdb
, PCWSTR layerName
, PSDBQUERYRESULT result
)
220 TAGID database
, layer
;
224 database
= SdbFindFirstTag(db
, TAGID_ROOT
, TAG_DATABASE
);
225 if (database
== TAGID_NULL
)
228 layer
= SdbFindFirstNamedTag(db
, database
, TAG_LAYER
, TAG_NAME
, layerName
);
229 if (layer
== TAGID_NULL
)
232 if (!SdbTagIDToTagRef(hsdb
, db
, layer
, &tr
))
235 if (!SdbpAddSingleLayerMatch(tr
, result
))
238 SdbpAddDatabaseGuid(db
, result
);
242 static void SdbpAddExeLayers(HSDB hsdb
, PDB db
, TAGID tagExe
, PSDBQUERYRESULT result
)
244 TAGID layer
= SdbFindFirstTag(db
, tagExe
, TAG_LAYER
);
246 while (layer
!= TAGID_NULL
)
249 TAGID layerIdTag
= SdbFindFirstTag(db
, layer
, TAG_LAYER_TAGID
);
250 DWORD tagId
= SdbReadDWORDTag(db
, layerIdTag
, TAGID_NULL
);
252 if (layerIdTag
!= TAGID_NULL
&&
253 tagId
!= TAGID_NULL
&&
254 SdbTagIDToTagRef(hsdb
, db
, tagId
, &tr
))
256 SdbpAddSingleLayerMatch(tr
, result
);
260 /* Try a name lookup */
261 TAGID layerTag
= SdbFindFirstTag(db
, layer
, TAG_NAME
);
262 if (layerTag
!= TAGID_NULL
)
264 LPCWSTR layerName
= SdbGetStringTagPtr(db
, layerTag
);
267 SdbpAddNamedLayerMatch(hsdb
, layerName
, result
);
272 layer
= SdbFindNextTag(db
, tagExe
, layer
);
276 static void SdbpAddExeMatch(HSDB hsdb
, PDB db
, TAGID tagExe
, PSDBQUERYRESULT result
)
281 if (!SdbTagIDToTagRef(hsdb
, db
, tagExe
, &tr
))
284 for (n
= 0; n
< result
->dwExeCount
; ++n
)
286 if (result
->atrExes
[n
] == tr
)
290 if (n
>= _countof(result
->atrExes
))
293 result
->atrExes
[n
] = tr
;
294 result
->dwExeCount
++;
296 SdbpAddExeLayers(hsdb
, db
, tagExe
, result
);
298 SdbpAddDatabaseGuid(db
, result
);
301 static ULONG
SdbpAddLayerMatches(HSDB hsdb
, PWSTR pwszLayers
, DWORD pdwBytes
, PSDBQUERYRESULT result
)
303 PWSTR start
= pwszLayers
, p
;
306 const PWSTR end
= pwszLayers
+ (pdwBytes
/ sizeof(WCHAR
));
307 while (start
< end
&& (*start
== L
'!' || *start
== L
'#' || *start
== L
' ' || *start
== L
'\t'))
315 while (*start
== L
' ' || *start
== L
'\t')
318 if (*start
== UNICODE_NULL
)
320 p
= wcspbrk(start
, L
" \t");
325 if (SdbpAddNamedLayerMatch(hsdb
, start
, result
))
329 } while (start
< end
&& p
);
334 static BOOL
SdbpPropagateEnvLayers(HSDB hsdb
, LPWSTR Environment
, PSDBQUERYRESULT Result
)
336 static const UNICODE_STRING EnvKey
= RTL_CONSTANT_STRING(L
"__COMPAT_LAYER");
337 UNICODE_STRING EnvValue
;
339 WCHAR Buffer
[MAX_LAYER_LENGTH
];
341 RtlInitEmptyUnicodeString(&EnvValue
, Buffer
, sizeof(Buffer
));
343 Status
= RtlQueryEnvironmentVariable_U(Environment
, &EnvKey
, &EnvValue
);
345 if (!NT_SUCCESS(Status
))
348 return SdbpAddLayerMatches(hsdb
, Buffer
, EnvValue
.Length
, Result
) > 0;
354 * Opens specified shim database file Handle returned by this function may only be used by
355 * functions which take HSDB param thus differing it from SdbOpenDatabase.
357 * @param [in] flags Specifies type of path or predefined database.
358 * @param [in] path Path to the shim database file.
360 * @return Success: Handle to the opened shim database, NULL otherwise.
362 HSDB WINAPI
SdbInitDatabase(DWORD flags
, LPCWSTR path
)
364 static const WCHAR shim
[] = {'\\','s','y','s','m','a','i','n','.','s','d','b',0};
365 static const WCHAR msi
[] = {'\\','m','s','i','m','a','i','n','.','s','d','b',0};
366 static const WCHAR drivers
[] = {'\\','d','r','v','m','a','i','n','.','s','d','b',0};
371 hsdb
= SdbAlloc(sizeof(SDB
));
374 hsdb
->auto_loaded
= 0;
376 /* Check for predefined databases */
377 if ((flags
& HID_DATABASE_TYPE_MASK
) && path
== NULL
)
379 switch (flags
& HID_DATABASE_TYPE_MASK
)
381 case SDB_DATABASE_MAIN_SHIM
: name
= shim
; break;
382 case SDB_DATABASE_MAIN_MSI
: name
= msi
; break;
383 case SDB_DATABASE_MAIN_DRIVERS
: name
= drivers
; break;
385 SdbReleaseDatabase(hsdb
);
388 SdbGetAppPatchDir(NULL
, buffer
, 128);
389 memcpy(buffer
+ lstrlenW(buffer
), name
, SdbpStrsize(name
));
390 flags
= HID_DOS_PATHS
;
393 hsdb
->db
= SdbOpenDatabase(path
? path
: buffer
, (flags
& 0xF) - 1);
395 /* If database could not be loaded, a handle doesn't make sense either */
398 SdbReleaseDatabase(hsdb
);
406 * Closes shim database opened by SdbInitDatabase.
408 * @param [in] hsdb Handle to the shim database.
410 void WINAPI
SdbReleaseDatabase(HSDB hsdb
)
412 SdbCloseDatabase(hsdb
->db
);
417 * Queries database for a specified exe If hsdb is NULL default database shall be loaded and
420 * @param [in] hsdb Handle to the shim database.
421 * @param [in] path Path to executable for which we query database.
422 * @param [in] module_name Unused.
423 * @param [in] env The environment block to use
424 * @param [in] flags 0 or SDBGMEF_IGNORE_ENVIRONMENT.
425 * @param [out] result Pointer to structure in which query result shall be stored.
427 * @return TRUE if it succeeds, FALSE if it fails.
429 BOOL WINAPI
SdbGetMatchingExe(HSDB hsdb
, LPCWSTR path
, LPCWSTR module_name
,
430 LPCWSTR env
, DWORD flags
, PSDBQUERYRESULT result
)
433 TAGID database
, iter
, name
;
434 PATTRINFO attribs
= NULL
;
436 RTL_UNICODE_STRING_BUFFER DosApplicationName
= { { 0 } };
437 WCHAR DosPathBuffer
[MAX_PATH
];
440 WCHAR wszLayers
[MAX_LAYER_LENGTH
];
444 /* Load default database if one is not specified */
447 /* To reproduce windows behaviour HID_DOS_PATHS needs
448 * to be specified when loading default database */
449 hsdb
= SdbInitDatabase(HID_DOS_PATHS
| SDB_DATABASE_MAIN_SHIM
, NULL
);
451 hsdb
->auto_loaded
= TRUE
;
454 ZeroMemory(result
, sizeof(*result
));
456 /* No database could be loaded */
460 /* We do not support multiple db's yet! */
463 RtlInitUnicodeString(&DosApplicationName
.String
, path
);
464 RtlInitBuffer(&DosApplicationName
.ByteBuffer
, (PUCHAR
)DosPathBuffer
, sizeof(DosPathBuffer
));
465 if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY
, &DosApplicationName
.ByteBuffer
, DosApplicationName
.String
.MaximumLength
)))
467 SHIM_ERR("Failed to convert allocate buffer.");
470 /* Update the internal buffer to contain the string */
471 memcpy(DosApplicationName
.ByteBuffer
.Buffer
, path
, DosApplicationName
.String
.MaximumLength
);
472 /* Make sure the string uses our internal buffer (we want to modify the buffer,
473 and RtlNtPathNameToDosPathName does not always modify the String to point to the Buffer)! */
474 DosApplicationName
.String
.Buffer
= (PWSTR
)DosApplicationName
.ByteBuffer
.Buffer
;
476 if (!NT_SUCCESS(RtlNtPathNameToDosPathName(0, &DosApplicationName
, &PathType
, NULL
)))
478 SHIM_ERR("Failed to convert %S to DOS Path.", path
);
483 /* Extract file name */
484 file_name
= strrchrW(DosApplicationName
.String
.Buffer
, '\\');
487 SHIM_ERR("Failed to find Exe name in %wZ.", &DosApplicationName
.String
);
491 /* We will use the buffer for exe name and directory. */
492 *(file_name
++) = UNICODE_NULL
;
494 /* DATABASE is list TAG which contains all executables */
495 database
= SdbFindFirstTag(db
, TAGID_ROOT
, TAG_DATABASE
);
496 if (database
== TAGID_NULL
)
501 /* EXE is list TAG which contains data required to match executable */
502 iter
= SdbFindFirstTag(db
, database
, TAG_EXE
);
504 /* Search for entry in database, we should look into indexing tags! */
505 while (iter
!= TAGID_NULL
)
508 /* Check if exe name matches */
509 name
= SdbFindFirstTag(db
, iter
, TAG_NAME
);
510 /* If this is a malformed DB, (no TAG_NAME), we should not crash. */
511 foundName
= SdbGetStringTagPtr(db
, name
);
512 if (foundName
&& !lstrcmpiW(foundName
, file_name
))
514 /* Get information about executable required to match it with database entry */
517 if (!SdbGetFileAttributes(path
, &attribs
, &attr_count
))
522 /* We have a null terminator before the application name, so DosApplicationName only contains the path. */
523 if (SdbpMatchExe(db
, iter
, DosApplicationName
.String
.Buffer
, attribs
, attr_count
))
526 SdbpAddExeMatch(hsdb
, db
, iter
, result
);
530 /* Continue iterating */
531 iter
= SdbFindNextTag(db
, database
, iter
);
534 /* Restore the full path. */
535 *(--file_name
) = L
'\\';
537 dwSize
= sizeof(wszLayers
);
538 if (SdbGetPermLayerKeys(DosApplicationName
.String
.Buffer
, wszLayers
, &dwSize
, GPLK_MACHINE
| GPLK_USER
))
540 SdbpAddLayerMatches(hsdb
, wszLayers
, dwSize
, result
);
544 if (!(flags
& SDBGMEF_IGNORE_ENVIRONMENT
))
546 if (SdbpPropagateEnvLayers(hsdb
, (LPWSTR
)env
, result
))
549 result
->dwFlags
|= SHIMREG_HAS_ENVIRONMENT
;
554 RtlFreeBuffer(&DosApplicationName
.ByteBuffer
);
556 SdbFreeFileAttributes(attribs
);
557 if (hsdb
->auto_loaded
)
558 SdbReleaseDatabase(hsdb
);
563 * Retrieves AppPatch directory.
565 * @param [in] db Handle to the shim database.
566 * @param [out] path Pointer to memory in which path shall be written.
567 * @param [in] size Size of the buffer in characters.
569 BOOL WINAPI
SdbGetAppPatchDir(HSDB db
, LPWSTR path
, DWORD size
)
571 static WCHAR
* default_dir
= NULL
;
572 static CONST WCHAR szAppPatch
[] = {'\\','A','p','p','P','a','t','c','h',0};
574 /* In case function fails, path holds empty string */
581 UINT len
= GetSystemWindowsDirectoryW(NULL
, 0) + lstrlenW(szAppPatch
);
582 tmp
= SdbAlloc((len
+ 1)* sizeof(WCHAR
));
585 UINT r
= GetSystemWindowsDirectoryW(tmp
, len
+1);
588 if (SUCCEEDED(StringCchCatW(tmp
, len
+1, szAppPatch
)))
590 if (InterlockedCompareExchangePointer((void**)&default_dir
, tmp
, NULL
) == NULL
)
599 SHIM_ERR("Unable to obtain default AppPatch directory\n");
606 return SUCCEEDED(StringCchCopyW(path
, size
, default_dir
));
610 SHIM_ERR("Unimplemented for db != NULL\n");
617 * Translates the given trWhich to a specific database / tagid
619 * @param [in] hsdb Handle to the database.
620 * @param [in] trWhich Tagref to find
621 * @param [out,opt] ppdb The Shim database that trWhich belongs to.
622 * @param [out,opt] ptiWhich The tagid that trWhich corresponds to.
624 * @return TRUE if it succeeds, FALSE if it fails.
626 BOOL WINAPI
SdbTagRefToTagID(HSDB hsdb
, TAGREF trWhich
, PDB
* ppdb
, TAGID
* ptiWhich
)
628 if (trWhich
& 0xf0000000)
630 SHIM_ERR("Multiple shim databases not yet implemented!\n");
634 *ptiWhich
= TAG_NULL
;
638 /* There seems to be no range checking on trWhich.. */
642 *ptiWhich
= trWhich
& 0x0fffffff;
648 * Translates the given trWhich to a specific database / tagid
650 * @param [in] hsdb Handle to the database.
651 * @param [in] pdb The Shim database that tiWhich belongs to.
652 * @param [in] tiWhich Path to executable for which we query database.
653 * @param [out,opt] ptrWhich The tagid that tiWhich corresponds to.
655 * @return TRUE if it succeeds, FALSE if it fails.
657 BOOL WINAPI
SdbTagIDToTagRef(HSDB hsdb
, PDB pdb
, TAGID tiWhich
, TAGREF
* ptrWhich
)
661 SHIM_ERR("Multiple shim databases not yet implemented!\n");
663 *ptrWhich
= TAGREF_NULL
;
668 *ptrWhich
= tiWhich
& 0x0fffffff;
675 BOOL WINAPI
SdbPackAppCompatData(HSDB hsdb
, PSDBQUERYRESULT pQueryResult
, PVOID
* ppData
, DWORD
*pdwSize
)
681 if (!pQueryResult
|| !ppData
|| !pdwSize
)
683 SHIM_WARN("Invalid params: %p, %p, %p\n", pQueryResult
, ppData
, pdwSize
);
687 pData
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(ShimData
));
690 SHIM_WARN("Unable to allocate %d bytes\n", sizeof(ShimData
));
694 GetWindowsDirectoryW(pData
->szModule
, _countof(pData
->szModule
));
695 hr
= StringCchCatW(pData
->szModule
, _countof(pData
->szModule
), L
"\\system32\\apphelp.dll");
698 SHIM_ERR("Unable to append module name (0x%x)\n", hr
);
699 RtlFreeHeap(RtlGetProcessHeap(), 0, pData
);
703 pData
->dwSize
= sizeof(*pData
);
704 pData
->dwMagic
= SHIMDATA_MAGIC
;
705 pData
->Query
= *pQueryResult
;
707 pData
->szLayer
[0] = UNICODE_NULL
; /* TODO */
709 SHIM_INFO("\ndwFlags 0x%x\ndwMagic 0x%x\ntrExe 0x%x\ntrLayer 0x%x\n",
710 pData
->Query
.dwFlags
, pData
->dwMagic
, pData
->Query
.atrExes
[0], pData
->Query
.atrLayers
[0]);
713 /* 0x0 {GUID} NAME */
715 for (n
= 0; n
< pQueryResult
->dwLayerCount
; ++n
)
717 SHIM_INFO("Layer 0x%x\n", pQueryResult
->atrLayers
[n
]);
721 *pdwSize
= pData
->dwSize
;
726 BOOL WINAPI
SdbUnpackAppCompatData(HSDB hsdb
, LPCWSTR pszImageName
, PVOID pData
, PSDBQUERYRESULT pQueryResult
)
728 ShimData
* pShimData
= pData
;
730 if (!pShimData
|| pShimData
->dwMagic
!= SHIMDATA_MAGIC
|| pShimData
->dwSize
< sizeof(ShimData
))
738 *pQueryResult
= pShimData
->Query
;
742 DWORD WINAPI
SdbGetAppCompatDataSize(ShimData
* pData
)
744 if (!pData
|| pData
->dwMagic
!= SHIMDATA_MAGIC
)
748 return pData
->dwSize
;