1 /*
2 * PROJECT: ReactOS Applications Manager
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Classes for working with available applications
5 * COPYRIGHT: Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
6 * Copyright 2020 He Yang (1160386205@qq.com)
7 * Copyright 2021-2023 Mark Jansen <mark.jansen@reactos.org>
8 */
9
10 #include "rapps.h"
11 #include "appdb.h"
12 #include "configparser.h"
13 #include "settings.h"
14
15
16 static HKEY g_RootKeyEnum[3] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_LOCAL_MACHINE};
17 static REGSAM g_RegSamEnum[3] = {0, KEY_WOW64_32KEY, KEY_WOW64_64KEY};
18 #define UNINSTALL_SUBKEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
19
20 static VOID
ClearList(CAtlList<CAppInfo * > & list)21 ClearList(CAtlList<CAppInfo *> &list)
22 {
23 POSITION InfoListPosition = list.GetHeadPosition();
24 while (InfoListPosition)
25 {
26 CAppInfo *Info = list.GetNext(InfoListPosition);
27 delete Info;
28 }
29 list.RemoveAll();
30 }
31
CAppDB(const CStringW & path)32 CAppDB::CAppDB(const CStringW &path) : m_BasePath(path)
33 {
34 m_BasePath.Canonicalize();
35 }
36
37 CAvailableApplicationInfo *
FindAvailableByPackageName(const CStringW & name)38 CAppDB::FindAvailableByPackageName(const CStringW &name)
39 {
40 POSITION CurrentListPosition = m_Available.GetHeadPosition();
41 while (CurrentListPosition)
42 {
43 CAppInfo *Info = m_Available.GetNext(CurrentListPosition);
44 if (Info->szIdentifier == name)
45 {
46 return static_cast<CAvailableApplicationInfo *>(Info);
47 }
48 }
49 return NULL;
50 }
51
52 void
GetApps(CAtlList<CAppInfo * > & List,AppsCategories Type) const53 CAppDB::GetApps(CAtlList<CAppInfo *> &List, AppsCategories Type) const
54 {
55 const BOOL UseInstalled = IsInstalledEnum(Type);
56 const CAtlList<CAppInfo *> &list = UseInstalled ? m_Installed : m_Available;
57 const BOOL IncludeAll = UseInstalled ? (Type == ENUM_ALL_INSTALLED) : (Type == ENUM_ALL_AVAILABLE);
58
59 POSITION CurrentListPosition = list.GetHeadPosition();
60 while (CurrentListPosition)
61 {
62 CAppInfo *Info = list.GetNext(CurrentListPosition);
63
64 if (IncludeAll || Type == Info->iCategory)
65 {
66 List.AddTail(Info);
67 }
68 }
69 }
70
71 BOOL
EnumerateFiles()72 CAppDB::EnumerateFiles()
73 {
74 ClearList(m_Available);
75
76 CPathW AppsPath = m_BasePath;
77 AppsPath += RAPPS_DATABASE_SUBDIR;
78 CPathW WildcardPath = AppsPath;
79 WildcardPath += L"*.txt";
80
81 WIN32_FIND_DATAW FindFileData;
82 HANDLE hFind = FindFirstFileW(WildcardPath, &FindFileData);
83 if (hFind == INVALID_HANDLE_VALUE)
84 {
85 return FALSE;
86 }
87
88 do
89 {
90 CStringW szPkgName = FindFileData.cFileName;
91 PathRemoveExtensionW(szPkgName.GetBuffer(MAX_PATH));
92 szPkgName.ReleaseBuffer();
93
94 CAppInfo *Info = FindByPackageName(szPkgName);
95 ATLASSERT(Info == NULL);
96 if (!Info)
97 {
98 CConfigParser *Parser = new CConfigParser(CPathW(AppsPath) += FindFileData.cFileName);
99 int Cat;
100 if (!Parser->GetInt(DB_CATEGORY, Cat))
101 Cat = ENUM_INVALID;
102
103 Info = new CAvailableApplicationInfo(Parser, szPkgName, static_cast<AppsCategories>(Cat), AppsPath);
104 if (Info->Valid())
105 {
106 m_Available.AddTail(Info);
107 }
108 else
109 {
110 delete Info;
111 }
112 }
113
114 } while (FindNextFileW(hFind, &FindFileData));
115
116 FindClose(hFind);
117 return TRUE;
118 }
119
120 VOID
UpdateAvailable()121 CAppDB::UpdateAvailable()
122 {
123 if (!CreateDirectoryW(m_BasePath, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
124 return;
125
126 if (EnumerateFiles())
127 return;
128
129 DownloadApplicationsDB(
130 SettingsInfo.bUseSource ? SettingsInfo.szSourceURL : APPLICATION_DATABASE_URL, !SettingsInfo.bUseSource);
131
132 CPathW AppsPath = m_BasePath;
133 AppsPath += RAPPS_DATABASE_SUBDIR;
134 if (!ExtractFilesFromCab(APPLICATION_DATABASE_NAME, m_BasePath, AppsPath))
135 return;
136
137 CPathW CabFile = m_BasePath;
138 CabFile += APPLICATION_DATABASE_NAME;
139 DeleteFileW(CabFile);
140
141 EnumerateFiles();
142 }
143
144 static inline HKEY
GetRootKeyInfo(UINT Index,REGSAM & RegSam)145 GetRootKeyInfo(UINT Index, REGSAM &RegSam)
146 {
147 C_ASSERT(_countof(g_RootKeyEnum) == _countof(g_RegSamEnum));
148 if (Index < _countof(g_RootKeyEnum))
149 {
150 RegSam = g_RegSamEnum[Index];
151 return g_RootKeyEnum[Index];
152 }
153 return NULL;
154 }
155
156 HKEY
EnumInstalledRootKey(UINT Index,REGSAM & RegSam)157 CAppDB::EnumInstalledRootKey(UINT Index, REGSAM &RegSam)
158 {
159 // Loop for through all combinations.
160 // Note that HKEY_CURRENT_USER\Software does not have a redirect
161 // https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys#redirected-shared-and-reflected-keys-under-wow64
162 if (Index < (IsSystem64Bit() ? 3 : 2))
163 return GetRootKeyInfo(Index, RegSam);
164 else
165 return NULL;
166 }
167
168 CInstalledApplicationInfo *
CreateInstalledAppByRegistryKey(LPCWSTR KeyName,HKEY hKeyParent,UINT KeyIndex)169 CAppDB::CreateInstalledAppByRegistryKey(LPCWSTR KeyName, HKEY hKeyParent, UINT KeyIndex)
170 {
171 CRegKey hSubKey;
172 if (hSubKey.Open(hKeyParent, KeyName, KEY_READ) != ERROR_SUCCESS)
173 return NULL;
174 DWORD value, size;
175
176 size = sizeof(DWORD);
177 if (!RegQueryValueExW(hSubKey, L"SystemComponent", NULL, NULL, (LPBYTE)&value, &size) && value == 1)
178 {
179 // Ignore system components
180 return NULL;
181 }
182
183 size = 0;
184 BOOL bIsUpdate = !RegQueryValueExW(hSubKey, L"ParentKeyName", NULL, NULL, NULL, &size);
185
186 AppsCategories cat = bIsUpdate ? ENUM_UPDATES : ENUM_INSTALLED_APPLICATIONS;
187 CInstalledApplicationInfo *pInfo;
188 pInfo = new CInstalledApplicationInfo(hSubKey.Detach(), KeyName, cat, KeyIndex);
189 if (pInfo && pInfo->Valid())
190 {
191 return pInfo;
192 }
193 delete pInfo;
194 return NULL;
195 }
196
197 CInstalledApplicationInfo *
EnumerateRegistry(CAtlList<CAppInfo * > * List,LPCWSTR SearchOnly)198 CAppDB::EnumerateRegistry(CAtlList<CAppInfo *> *List, LPCWSTR SearchOnly)
199 {
200 ATLASSERT(List || SearchOnly);
201 REGSAM wowsam;
202 HKEY hRootKey;
203 for (UINT rki = 0; (hRootKey = EnumInstalledRootKey(rki, wowsam)); ++rki)
204 {
205 CRegKey hKey;
206 if (hKey.Open(hRootKey, UNINSTALL_SUBKEY, KEY_READ | wowsam) != ERROR_SUCCESS)
207 {
208 continue;
209 }
210 for (DWORD Index = 0;; ++Index)
211 {
212 WCHAR szKeyName[MAX_PATH];
213 DWORD dwSize = _countof(szKeyName);
214 if (hKey.EnumKey(Index, szKeyName, &dwSize) != ERROR_SUCCESS)
215 {
216 break;
217 }
218 if (List || !StrCmpIW(SearchOnly, szKeyName))
219 {
220 CInstalledApplicationInfo *Info;
221 Info = CreateInstalledAppByRegistryKey(szKeyName, hKey, rki);
222 if (Info)
223 {
224 if (List)
225 List->AddTail(Info);
226 else
227 return Info;
228 }
229 }
230 }
231 }
232 return NULL;
233 }
234
235 VOID
UpdateInstalled()236 CAppDB::UpdateInstalled()
237 {
238 // Remove all old entries
239 ClearList(m_Installed);
240
241 EnumerateRegistry(&m_Installed, NULL);
242 }
243
244 CInstalledApplicationInfo *
CreateInstalledAppByRegistryKey(LPCWSTR Name)245 CAppDB::CreateInstalledAppByRegistryKey(LPCWSTR Name)
246 {
247 return EnumerateRegistry(NULL, Name);
248 }
249
250 CInstalledApplicationInfo *
CreateInstalledAppInstance(LPCWSTR KeyName,BOOL User,REGSAM WowSam)251 CAppDB::CreateInstalledAppInstance(LPCWSTR KeyName, BOOL User, REGSAM WowSam)
252 {
253 HKEY hRootKey = User ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
254 UINT KeyIndex = User ? (0) : ((WowSam & KEY_WOW64_64KEY) ? 2 : 1);
255 CRegKey hKey;
256 if (hKey.Open(hRootKey, UNINSTALL_SUBKEY, KEY_READ | WowSam) == ERROR_SUCCESS)
257 {
258 return CreateInstalledAppByRegistryKey(KeyName, hKey, KeyIndex);
259 }
260 return NULL;
261 }
262
263 static void
DeleteWithWildcard(const CPathW & Dir,const CStringW & Filter)264 DeleteWithWildcard(const CPathW &Dir, const CStringW &Filter)
265 {
266 HANDLE hFind = INVALID_HANDLE_VALUE;
267 WIN32_FIND_DATAW FindFileData;
268
269 CPathW DirWithFilter = Dir;
270 DirWithFilter += Filter;
271
272 hFind = FindFirstFileW(DirWithFilter, &FindFileData);
273
274 if (hFind == INVALID_HANDLE_VALUE)
275 return;
276
277 do
278 {
279 CPathW szTmp = Dir;
280 szTmp += FindFileData.cFileName;
281
282 if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
283 {
284 DeleteFileW(szTmp);
285 }
286 } while (FindNextFileW(hFind, &FindFileData) != 0);
287 FindClose(hFind);
288 }
289
290 VOID
RemoveCached()291 CAppDB::RemoveCached()
292 {
293 // Delete icons
294 CPathW AppsPath = m_BasePath;
295 AppsPath += RAPPS_DATABASE_SUBDIR;
296 CPathW IconPath = AppsPath;
297 IconPath += L"icons";
298 DeleteWithWildcard(IconPath, L"*.ico");
299
300 // Delete leftover screenshots
301 CPathW ScrnshotFolder = AppsPath;
302 ScrnshotFolder += L"screenshots";
303 DeleteWithWildcard(ScrnshotFolder, L"*.tmp");
304
305 // Delete data base files (*.txt)
306 DeleteWithWildcard(AppsPath, L"*.txt");
307
308 RemoveDirectoryW(IconPath);
309 RemoveDirectoryW(ScrnshotFolder);
310 RemoveDirectoryW(AppsPath);
311 RemoveDirectoryW(m_BasePath);
312 }
313
314 DWORD
RemoveInstalledAppFromRegistry(const CAppInfo * Info)315 CAppDB::RemoveInstalledAppFromRegistry(const CAppInfo *Info)
316 {
317 // Validate that this is actually an installed app / update
318 ATLASSERT(Info->iCategory == ENUM_INSTALLED_APPLICATIONS || Info->iCategory == ENUM_UPDATES);
319 if (Info->iCategory != ENUM_INSTALLED_APPLICATIONS && Info->iCategory != ENUM_UPDATES)
320 return ERROR_INVALID_PARAMETER;
321
322 const CInstalledApplicationInfo *InstalledInfo = static_cast<const CInstalledApplicationInfo *>(Info);
323
324 CStringW Name = InstalledInfo->szIdentifier;
325 REGSAM wowsam;
326 HKEY hRoot = GetRootKeyInfo(InstalledInfo->m_KeyInfo, wowsam);
327 ATLASSERT(hRoot);
328 if (!hRoot)
329 return ERROR_OPEN_FAILED;
330
331 CRegKey Uninstall;
332 LSTATUS err = Uninstall.Open(hRoot, UNINSTALL_SUBKEY, KEY_READ | KEY_WRITE | wowsam);
333 if (err == ERROR_SUCCESS)
334 {
335 err = Uninstall.RecurseDeleteKey(Name);
336 }
337 return err;
338 }
339