1 /*
2  * PROJECT:     ReactOS CTF Monitor
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Registry watcher
5  * COPYRIGHT:   Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 #include "CRegWatcher.h"
10 
11 // The event handles to use in watching
12 HANDLE CRegWatcher::s_ahWatchEvents[WATCHENTRY_MAX] = { NULL };
13 
14 // The registry entries to watch
15 WATCHENTRY CRegWatcher::s_WatchEntries[WATCHENTRY_MAX] =
16 {
17     { HKEY_CURRENT_USER,  TEXT("Keyboard Layout\\Toggle")                           }, // WI_TOGGLE
18     { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\CTF\\TIP")                     }, // WI_MACHINE_TIF
19     { HKEY_CURRENT_USER,  TEXT("Keyboard Layout\\Preload")                          }, // WI_PRELOAD
20     { HKEY_CURRENT_USER,  TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") }, // WI_RUN
21     { HKEY_CURRENT_USER,  TEXT("SOFTWARE\\Microsoft\\CTF\\TIP")                     }, // WI_USER_TIF
22     { HKEY_CURRENT_USER,  TEXT("SOFTWARE\\Microsoft\\Speech")                       }, // WI_USER_SPEECH
23     { HKEY_CURRENT_USER,  TEXT("Control Panel\\Appearance")                         }, // WI_APPEARANCE
24     { HKEY_CURRENT_USER,  TEXT("Control Panel\\Colors")                             }, // WI_COLORS
25     { HKEY_CURRENT_USER,  TEXT("Control Panel\\Desktop\\WindowMetrics")             }, // WI_WINDOW_METRICS
26     { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Speech")                       }, // WI_MACHINE_SPEECH
27     { HKEY_CURRENT_USER,  TEXT("Keyboard Layout")                                   }, // WI_KEYBOARD_LAYOUT
28     { HKEY_CURRENT_USER,  TEXT("SOFTWARE\\Microsoft\\CTF\\Assemblies")              }, // WI_ASSEMBLIES
29 };
30 
31 // The timer IDs: For delaying ignitions
32 UINT CRegWatcher::s_nSysColorTimerId    = 0;
33 UINT CRegWatcher::s_nKbdToggleTimerId   = 0;
34 UINT CRegWatcher::s_nRegImxTimerId      = 0;
35 
36 // %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil
37 typedef HRESULT (WINAPI* FN_TF_CreateLangProfileUtil)(ITfFnLangProfileUtil**);
38 
39 BOOL
Init()40 CRegWatcher::Init()
41 {
42     // NOTE: We don't support Win95/98/Me
43 #ifdef SUPPORT_WIN9X
44     if (!(g_dwOsInfo & CIC_OSINFO_NT))
45         s_WatchEntries[WI_RUN].hRootKey = HKEY_LOCAL_MACHINE;
46 #endif
47 
48     // Create some nameless events and initialize them
49     for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent)
50     {
51         s_ahWatchEvents[iEvent] = ::CreateEvent(NULL, TRUE, FALSE, NULL);
52         InitEvent(iEvent, FALSE);
53     }
54 
55     // Internat.exe is an enemy of ctfmon.exe
56     KillInternat();
57 
58     UpdateSpTip();
59 
60     return TRUE;
61 }
62 
63 VOID
Uninit()64 CRegWatcher::Uninit()
65 {
66     for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent)
67     {
68         // Close the key
69         WATCHENTRY& entry = s_WatchEntries[iEvent];
70         if (entry.hKey)
71         {
72             ::RegCloseKey(entry.hKey);
73             entry.hKey = NULL;
74         }
75 
76         // Close the event handle
77         HANDLE& hEvent = s_ahWatchEvents[iEvent];
78         if (hEvent)
79         {
80             ::CloseHandle(hEvent);
81             hEvent = NULL;
82         }
83     }
84 }
85 
86 // advapi32!RegNotifyChangeKeyValue
87 typedef LONG (WINAPI *FN_RegNotifyChangeKeyValue)(HKEY, BOOL, DWORD, HANDLE, BOOL);
88 
89 LONG WINAPI
DelayedRegNotifyChangeKeyValue(HKEY hKey,BOOL bWatchSubtree,DWORD dwNotifyFilter,HANDLE hEvent,BOOL fAsynchronous)90 DelayedRegNotifyChangeKeyValue(
91     HKEY hKey,
92     BOOL bWatchSubtree,
93     DWORD dwNotifyFilter,
94     HANDLE hEvent,
95     BOOL fAsynchronous)
96 {
97     static FN_RegNotifyChangeKeyValue s_fnRegNotifyChangeKeyValue = NULL;
98 
99     if (!s_fnRegNotifyChangeKeyValue)
100     {
101         HINSTANCE hAdvApi32 = cicGetSystemModuleHandle(TEXT("advapi32.dll"), FALSE);
102         s_fnRegNotifyChangeKeyValue =
103             (FN_RegNotifyChangeKeyValue)GetProcAddress(hAdvApi32, "RegNotifyChangeKeyValue");
104         if (!s_fnRegNotifyChangeKeyValue)
105             return ERROR_CALL_NOT_IMPLEMENTED;
106     }
107 
108     return s_fnRegNotifyChangeKeyValue(hKey, bWatchSubtree, dwNotifyFilter, hEvent, fAsynchronous);
109 }
110 
111 BOOL
InitEvent(_In_ SIZE_T iEvent,_In_ BOOL bResetEvent)112 CRegWatcher::InitEvent(
113     _In_ SIZE_T iEvent,
114     _In_ BOOL bResetEvent)
115 {
116     // Reset the signal status
117     if (bResetEvent)
118         ::ResetEvent(s_ahWatchEvents[iEvent]);
119 
120     // Close once to re-open
121     WATCHENTRY& entry = s_WatchEntries[iEvent];
122     if (entry.hKey)
123     {
124         ::RegCloseKey(entry.hKey);
125         entry.hKey = NULL;
126     }
127 
128     // Open or create a registry key to watch registry key
129     LSTATUS error;
130     error = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_READ, &entry.hKey);
131     if (error != ERROR_SUCCESS)
132     {
133         error = ::RegCreateKeyEx(entry.hRootKey, entry.pszSubKey, 0, NULL, 0,
134                                  KEY_ALL_ACCESS, NULL, &entry.hKey, NULL);
135         if (error != ERROR_SUCCESS)
136             return FALSE;
137     }
138 
139     // Start registry watching
140     error = DelayedRegNotifyChangeKeyValue(entry.hKey,
141                                            TRUE,
142                                            REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME,
143                                            s_ahWatchEvents[iEvent],
144                                            TRUE);
145 #ifndef NDEBUG
146     if (error != ERROR_SUCCESS)
147         OutputDebugStringA("RegNotifyChangeKeyValue failed\n");
148 #endif
149     return error == ERROR_SUCCESS;
150 }
151 
152 VOID
UpdateSpTip()153 CRegWatcher::UpdateSpTip()
154 {
155     // Post message 0x8002 to "SapiTipWorkerClass" windows
156     ::EnumWindows(EnumWndProc, 0);
157 
158     // Clear "ProfileInitialized" value
159     HKEY hKey;
160     LSTATUS error = ::RegOpenKeyEx(HKEY_CURRENT_USER,
161                                    TEXT("SOFTWARE\\Microsoft\\CTF\\Sapilayr"),
162                                    0, KEY_WRITE, &hKey);
163     if (error == ERROR_SUCCESS)
164     {
165         DWORD dwValue = 0, cbValue = sizeof(dwValue);
166         ::RegSetValueEx(hKey, TEXT("ProfileInitialized"), NULL, REG_DWORD, (LPBYTE)&dwValue, cbValue);
167         ::RegCloseKey(hKey);
168     }
169 
170     // Get %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil function
171     HINSTANCE hSPTIP = cicLoadSystemLibrary(TEXT("IME\\sptip.dll"), TRUE);
172     FN_TF_CreateLangProfileUtil fnTF_CreateLangProfileUtil =
173         (FN_TF_CreateLangProfileUtil)::GetProcAddress(hSPTIP, "TF_CreateLangProfileUtil");
174     if (fnTF_CreateLangProfileUtil)
175     {
176         // Call it
177         ITfFnLangProfileUtil *pProfileUtil = NULL;
178         HRESULT hr = fnTF_CreateLangProfileUtil(&pProfileUtil);
179         if ((hr == S_OK) && pProfileUtil) // Success!
180         {
181             // Register profile
182             hr = pProfileUtil->RegisterActiveProfiles();
183             if (hr == S_OK)
184                 TF_InvalidAssemblyListCacheIfExist(); // Invalidate the assembly list cache
185 
186             pProfileUtil->Release();
187         }
188     }
189 
190     if (hSPTIP)
191         ::FreeLibrary(hSPTIP);
192 }
193 
194 VOID
KillInternat()195 CRegWatcher::KillInternat()
196 {
197     HKEY hKey;
198     WATCHENTRY& entry = s_WatchEntries[WI_RUN];
199 
200     // Delete internat.exe from registry "Run" key
201     LSTATUS error = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_ALL_ACCESS, &hKey);
202     if (error == ERROR_SUCCESS)
203     {
204         ::RegDeleteValue(hKey, TEXT("internat.exe"));
205         ::RegCloseKey(hKey);
206     }
207 
208     // Kill the "Indicator" window (that internat.exe creates)
209     HWND hwndInternat = ::FindWindow(TEXT("Indicator"), NULL);
210     if (hwndInternat)
211         ::PostMessage(hwndInternat, WM_CLOSE, 0, 0);
212 }
213 
214 // Post message 0x8002 to every "SapiTipWorkerClass" window.
215 // Called from CRegWatcher::UpdateSpTip
216 BOOL CALLBACK
EnumWndProc(_In_ HWND hWnd,_In_ LPARAM lParam)217 CRegWatcher::EnumWndProc(
218     _In_ HWND hWnd,
219     _In_ LPARAM lParam)
220 {
221     TCHAR ClassName[MAX_PATH];
222 
223     UNREFERENCED_PARAMETER(lParam);
224 
225     if (::GetClassName(hWnd, ClassName, _countof(ClassName)) &&
226         _tcsicmp(ClassName, TEXT("SapiTipWorkerClass")) == 0)
227     {
228         PostMessage(hWnd, 0x8002, 0, 0); // FIXME: Magic number
229     }
230 
231     return TRUE;
232 }
233 
234 VOID CALLBACK
SysColorTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)235 CRegWatcher::SysColorTimerProc(
236     _In_ HWND hwnd,
237     _In_ UINT uMsg,
238     _In_ UINT_PTR idEvent,
239     _In_ DWORD dwTime)
240 {
241     UNREFERENCED_PARAMETER(hwnd);
242     UNREFERENCED_PARAMETER(uMsg);
243     UNREFERENCED_PARAMETER(idEvent);
244     UNREFERENCED_PARAMETER(dwTime);
245 
246     // Cancel the timer
247     if (s_nSysColorTimerId)
248     {
249         ::KillTimer(NULL, s_nSysColorTimerId);
250         s_nSysColorTimerId = 0;
251     }
252 
253     TF_PostAllThreadMsg(15, 16);
254 }
255 
256 VOID
StartSysColorChangeTimer()257 CRegWatcher::StartSysColorChangeTimer()
258 {
259     // Call SysColorTimerProc 0.5 seconds later (Delayed)
260     if (s_nSysColorTimerId)
261     {
262         ::KillTimer(NULL, s_nSysColorTimerId);
263         s_nSysColorTimerId = 0;
264     }
265     s_nSysColorTimerId = ::SetTimer(NULL, 0, 500, SysColorTimerProc);
266 }
267 
268 VOID CALLBACK
RegImxTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)269 CRegWatcher::RegImxTimerProc(
270     _In_ HWND hwnd,
271     _In_ UINT uMsg,
272     _In_ UINT_PTR idEvent,
273     _In_ DWORD dwTime)
274 {
275     UNREFERENCED_PARAMETER(hwnd);
276     UNREFERENCED_PARAMETER(uMsg);
277     UNREFERENCED_PARAMETER(idEvent);
278     UNREFERENCED_PARAMETER(dwTime);
279 
280     // Cancel the timer
281     if (s_nRegImxTimerId)
282     {
283         ::KillTimer(NULL, s_nRegImxTimerId);
284         s_nRegImxTimerId = 0;
285     }
286 
287     TF_InvalidAssemblyListCache();
288     TF_PostAllThreadMsg(12, 16);
289 }
290 
291 VOID CALLBACK
KbdToggleTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)292 CRegWatcher::KbdToggleTimerProc(
293     _In_ HWND hwnd,
294     _In_ UINT uMsg,
295     _In_ UINT_PTR idEvent,
296     _In_ DWORD dwTime)
297 {
298     UNREFERENCED_PARAMETER(hwnd);
299     UNREFERENCED_PARAMETER(uMsg);
300     UNREFERENCED_PARAMETER(idEvent);
301     UNREFERENCED_PARAMETER(dwTime);
302 
303     // Cancel the timer
304     if (s_nKbdToggleTimerId)
305     {
306         ::KillTimer(NULL, s_nKbdToggleTimerId);
307         s_nKbdToggleTimerId = 0;
308     }
309 
310     TF_PostAllThreadMsg(11, 16);
311 }
312 
313 VOID
OnEvent(_In_ SIZE_T iEvent)314 CRegWatcher::OnEvent(
315     _In_ SIZE_T iEvent)
316 {
317     InitEvent(iEvent, TRUE);
318 
319     switch (iEvent)
320     {
321         case WI_TOGGLE:
322         {
323             // Call KbdToggleTimerProc 0.5 seconds later (Delayed)
324             if (s_nKbdToggleTimerId)
325             {
326                 ::KillTimer(NULL, s_nKbdToggleTimerId);
327                 s_nKbdToggleTimerId = 0;
328             }
329             s_nKbdToggleTimerId = ::SetTimer(NULL, 0, 500, KbdToggleTimerProc);
330             break;
331         }
332         case WI_MACHINE_TIF:
333         case WI_PRELOAD:
334         case WI_USER_TIF:
335         case WI_MACHINE_SPEECH:
336         case WI_KEYBOARD_LAYOUT:
337         case WI_ASSEMBLIES:
338         {
339             if (iEvent == WI_MACHINE_SPEECH)
340                 UpdateSpTip();
341 
342             // Call RegImxTimerProc 0.2 seconds later (Delayed)
343             if (s_nRegImxTimerId)
344             {
345                 ::KillTimer(NULL, s_nRegImxTimerId);
346                 s_nRegImxTimerId = 0;
347             }
348             s_nRegImxTimerId = ::SetTimer(NULL, 0, 200, RegImxTimerProc);
349             break;
350         }
351         case WI_RUN: // The "Run" key is changed
352         {
353             KillInternat(); // Deny internat.exe the right to live
354             break;
355         }
356         case WI_USER_SPEECH:
357         case WI_APPEARANCE:
358         case WI_COLORS:
359         case WI_WINDOW_METRICS:
360         {
361             StartSysColorChangeTimer();
362             break;
363         }
364         default:
365         {
366             break;
367         }
368     }
369 }
370