xref: /reactos/dll/appcompat/shims/shimlib/shimlib.c (revision 4567e13e)
1 /*
2  * PROJECT:     ReactOS Shim helper library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Shim helper functions
5  * COPYRIGHT:   Copyright 2016-2018 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #define WIN32_NO_STATUS
9 #include <windef.h>
10 #include <winbase.h>
11 #include <shimlib.h>
12 #include <strsafe.h>
13 #include <ndk/rtlfuncs.h>
14 
15 typedef struct UsedShim
16 {
17     SLIST_ENTRY Entry;
18     PSHIMREG pShim;
19 #if (WINVER > _WIN32_WINNT_WS03)
20     BOOL bInitCalled;
21 #endif
22 } UsedShim, *pUsedShim;
23 
24 
25 ULONG g_ShimEngDebugLevel = 0xffffffff;
26 static HANDLE g_ShimLib_Heap;
27 static PSLIST_HEADER g_UsedShims;
28 
29 void ShimLib_Init(HINSTANCE hInstance)
30 {
31     g_ShimLib_Heap = HeapCreate(0, 0x10000, 0);
32 
33     g_UsedShims = (PSLIST_HEADER)ShimLib_ShimMalloc(sizeof(SLIST_HEADER));
34     RtlInitializeSListHead(g_UsedShims);
35 }
36 
37 void ShimLib_Deinit()
38 {
39     // Is this a good idea?
40     HeapDestroy(g_ShimLib_Heap);
41 }
42 
43 PVOID ShimLib_ShimMalloc(SIZE_T dwSize)
44 {
45     return HeapAlloc(g_ShimLib_Heap, 0, dwSize);
46 }
47 
48 void ShimLib_ShimFree(PVOID pData)
49 {
50     HeapFree(g_ShimLib_Heap, 0, pData);
51 }
52 
53 PCSTR ShimLib_StringNDuplicateA(PCSTR szString, SIZE_T stringLengthIncludingNullTerm)
54 {
55     PSTR NewString = ShimLib_ShimMalloc(stringLengthIncludingNullTerm);
56     StringCchCopyA(NewString, stringLengthIncludingNullTerm, szString);
57     return NewString;
58 }
59 
60 PCSTR ShimLib_StringDuplicateA(PCSTR szString)
61 {
62     return ShimLib_StringNDuplicateA(szString, lstrlenA(szString) + 1);
63 }
64 
65 BOOL ShimLib_StrAEqualsW(PCSTR szString, PCWSTR wszString)
66 {
67     while (*szString == *wszString)
68     {
69         if (!*szString)
70             return TRUE;
71 
72         szString++; wszString++;
73     }
74     return FALSE;
75 }
76 
77 #if defined(_MSC_VER)
78 
79 #if defined(_M_IA64) || defined(_M_AMD64)
80 #define _ATTRIBUTES read
81 #else
82 #define _ATTRIBUTES read
83 #endif
84 
85 
86 #pragma section(".shm",long,read)
87 #pragma section(".shm$AAA",long,read)
88 #pragma section(".shm$ZZZ",long,read)
89 #endif
90 
91 #ifdef _MSC_VER
92 #pragma comment(linker, "/merge:.shm=.rdata")
93 #endif
94 
95 
96 _SHMALLOC(".shm") SHIMREG _shim_start = { 0 };
97 _SHMALLOC(".shm$ZZZ") SHIMREG _shim_end = { 0 };
98 
99 
100 /* Generic GetHookAPIs function.
101    The macro's from <setup_shim.inl> and <implement_shim.inl> will register a list of all apis that should be hooked
102    for a specific shim
103    This helper function will return the correct shim, and call the init function */
104 PHOOKAPI WINAPI ShimLib_GetHookAPIs(IN LPCSTR szCommandLine, IN LPCWSTR wszShimName, OUT PDWORD pdwHookCount)
105 {
106     PSHIMREG ps = &_shim_start;
107     ps++;
108     for (; ps != &_shim_end; ps++)
109     {
110         if (ps->GetHookAPIs != NULL && ps->ShimName != NULL)
111         {
112             if (ShimLib_StrAEqualsW(ps->ShimName, wszShimName))
113             {
114                 pUsedShim shim = (pUsedShim)ShimLib_ShimMalloc(sizeof(UsedShim));
115                 shim->pShim = ps;
116 #if (WINVER > _WIN32_WINNT_WS03)
117                 shim->bInitCalled = FALSE;
118 #endif
119                 RtlInterlockedPushEntrySList(g_UsedShims, &(shim->Entry));
120 
121                 return ps->GetHookAPIs(SHIM_NOTIFY_ATTACH, szCommandLine, pdwHookCount);
122             }
123         }
124     }
125     return NULL;
126 }
127 
128 
129 BOOL WINAPI ShimLib_NotifyShims(DWORD fdwReason, PVOID ptr)
130 {
131     PSLIST_ENTRY pEntry = RtlFirstEntrySList(g_UsedShims);
132 
133     if (fdwReason < SHIM_REASON_INIT)
134         fdwReason += (SHIM_REASON_INIT - SHIM_NOTIFY_ATTACH);
135 
136     while (pEntry)
137     {
138         pUsedShim pUsed = CONTAINING_RECORD(pEntry, UsedShim, Entry);
139         _PVNotify Notify = pUsed->pShim->Notify;
140 #if (WINVER > _WIN32_WINNT_WS03)
141         if (pUsed->bInitCalled && fdwReason == SHIM_REASON_INIT)
142             Notify = NULL;
143 #endif
144         if (Notify)
145             Notify(fdwReason, ptr);
146 #if (WINVER > _WIN32_WINNT_WS03)
147         if (fdwReason == SHIM_REASON_INIT)
148             pUsed->bInitCalled = TRUE;
149 #endif
150 
151         pEntry = pEntry->Next;
152     }
153 
154     return TRUE;
155 }
156 
157 
158 VOID SeiInitDebugSupport(VOID)
159 {
160     static const UNICODE_STRING DebugKey = RTL_CONSTANT_STRING(L"SHIM_DEBUG_LEVEL");
161     UNICODE_STRING DebugValue;
162     NTSTATUS Status;
163     ULONG NewLevel = SEI_MSG;
164     WCHAR Buffer[40];
165 
166     RtlInitEmptyUnicodeString(&DebugValue, Buffer, sizeof(Buffer));
167 
168     Status = RtlQueryEnvironmentVariable_U(NULL, &DebugKey, &DebugValue);
169 
170     if (NT_SUCCESS(Status))
171     {
172         if (!NT_SUCCESS(RtlUnicodeStringToInteger(&DebugValue, 10, &NewLevel)))
173             NewLevel = 0;
174     }
175     g_ShimEngDebugLevel = NewLevel;
176 }
177 
178 
179 /**
180 * Outputs diagnostic info.
181 *
182 * @param [in]  Level           The level to log this message with, choose any of [SHIM_ERR,
183 *                              SHIM_WARN, SHIM_INFO].
184 * @param [in]  FunctionName    The function this log should be attributed to.
185 * @param [in]  Format          The format string.
186 * @param   ...                 Variable arguments providing additional information.
187 *
188 * @return  Success: TRUE Failure: FALSE.
189 */
190 BOOL WINAPIV SeiDbgPrint(SEI_LOG_LEVEL Level, PCSTR Function, PCSTR Format, ...)
191 {
192     char Buffer[512];
193     char* Current = Buffer;
194     const char* LevelStr;
195     size_t Length = sizeof(Buffer);
196     va_list ArgList;
197     HRESULT hr;
198 
199     if (g_ShimEngDebugLevel == 0xffffffff)
200         SeiInitDebugSupport();
201 
202     if (Level > g_ShimEngDebugLevel)
203         return FALSE;
204 
205     switch (Level)
206     {
207     case SEI_MSG:
208         LevelStr = "MSG ";
209         break;
210     case SEI_FAIL:
211         LevelStr = "FAIL";
212         break;
213     case SEI_WARN:
214         LevelStr = "WARN";
215         break;
216     case SEI_INFO:
217         LevelStr = "INFO";
218         break;
219     default:
220         LevelStr = "USER";
221         break;
222     }
223 
224     if (Function)
225         hr = StringCchPrintfExA(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, "[%s] [%s] ", LevelStr, Function);
226     else
227         hr = StringCchPrintfExA(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, "[%s] ", LevelStr);
228 
229     if (!SUCCEEDED(hr))
230         return FALSE;
231 
232     va_start(ArgList, Format);
233     hr = StringCchVPrintfExA(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, Format, ArgList);
234     va_end(ArgList);
235     if (!SUCCEEDED(hr))
236         return FALSE;
237 
238     DbgPrint("%s", Buffer);
239     return TRUE;
240 }
241 
242